diff --git a/src/app/file/tga_format.cpp b/src/app/file/tga_format.cpp index 9521a5e33..2e58b01bc 100644 --- a/src/app/file/tga_format.cpp +++ b/src/app/file/tga_format.cpp @@ -70,9 +70,9 @@ FileFormat* CreateTgaFormat() namespace { -class TgaDecoderDelegate : public tga::DecoderDelegate { +class TgaDelegate : public tga::Delegate { public: - TgaDecoderDelegate(FileOp* fop) : m_fop(fop) { } + TgaDelegate(FileOp* fop) : m_fop(fop) { } bool notifyProgress(double progress) override { m_fop->setProgress(progress); return !m_fop->isStop(); @@ -123,10 +123,8 @@ bool get_image_spec(const tga::Header& header, ImageSpec& spec) bool TgaFormat::onLoad(FileOp* fop) { FileHandle handle(open_file_with_exception(fop->filename(), "rb")); - FILE* f = handle.get(); - tga::StdioFileInterface finterface(f); + tga::StdioFileInterface finterface(handle.get()); tga::Decoder decoder(&finterface); - tga::Header header; if (!decoder.readHeader(header)) { fop->setError("Invalid TGA header\n"); @@ -175,7 +173,7 @@ bool TgaFormat::onLoad(FileOp* fop) tgaImage.bytesPerPixel = image->getRowStrideSize(1); // Read image - TgaDecoderDelegate delegate(fop); + TgaDelegate delegate(fop); if (!decoder.readImage(header, tgaImage, &delegate)) { fop->setError("Error loading image data from TGA file.\n"); return false; @@ -202,7 +200,7 @@ bool TgaFormat::onLoad(FileOp* fop) opts->compress(header.isRle()); fop->setLoadedFormatOptions(opts); - if (ferror(f)) { + if (ferror(handle.get())) { fop->setError("Error reading file.\n"); return false; } @@ -215,277 +213,72 @@ bool TgaFormat::onLoad(FileOp* fop) namespace { -class TgaEncoder { -public: - TgaEncoder(FILE* f) - : m_f(f) { - } +void prepare_header(tga::Header& header, + const doc::Image* image, + const doc::Palette* palette, + const bool isOpaque, + const bool compressed, + int bitsPerPixel) +{ + header.idLength = 0; + header.colormapType = 0; + header.imageType = tga::NoImage; + header.colormapOrigin = 0; + header.colormapLength = 0; + header.colormapDepth = 0; + header.xOrigin = 0; + header.yOrigin = 0; + header.width = image->width(); + header.height = image->height(); + header.bitsPerPixel = 0; + // TODO make this option configurable + header.imageDescriptor = 0x20; // Top-to-bottom - void prepareHeader(const Image* image, - const Palette* palette, - const bool isOpaque, - const bool compressed, - int bitsPerPixel) { - m_header.idLength = 0; - m_header.colormapType = 0; - m_header.imageType = tga::NoImage; - m_header.colormapOrigin = 0; - m_header.colormapLength = 0; - m_header.colormapDepth = 0; - m_header.xOrigin = 0; - m_header.yOrigin = 0; - m_header.width = image->width(); - m_header.height = image->height(); - m_header.bitsPerPixel = 0; - // TODO make this option configurable - m_header.imageDescriptor = 0x20; // Top-to-bottom - - switch (image->colorMode()) { - case ColorMode::RGB: - m_header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb); - m_header.bitsPerPixel = (bitsPerPixel > 8 ? - bitsPerPixel: - (isOpaque ? 24: 32)); - if (!isOpaque) { - switch (m_header.bitsPerPixel) { - case 16: m_header.imageDescriptor |= 1; break; - case 32: m_header.imageDescriptor |= 8; break; - } + switch (image->colorMode()) { + case ColorMode::RGB: + header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb); + header.bitsPerPixel = (bitsPerPixel > 8 ? + bitsPerPixel: + (isOpaque ? 24: 32)); + if (!isOpaque) { + switch (header.bitsPerPixel) { + case 16: header.imageDescriptor |= 1; break; + case 32: header.imageDescriptor |= 8; break; } - break; - case ColorMode::GRAYSCALE: - // TODO if the grayscale is not opaque, we should use RGB, - // this could be done automatically in FileOp in a - // generic way for all formats when FILE_SUPPORT_RGBA is - // available and FILE_SUPPORT_GRAYA isn't. - m_header.imageType = (compressed ? tga::RleGray: tga::UncompressedGray); - m_header.bitsPerPixel = 8; - break; - case ColorMode::INDEXED: - ASSERT(palette); - - m_header.imageType = (compressed ? tga::RleIndexed: tga::UncompressedIndexed); - m_header.bitsPerPixel = 8; - m_header.colormapType = 1; - m_header.colormapLength = palette->size(); - if (palette->hasAlpha()) - m_header.colormapDepth = 32; - else - m_header.colormapDepth = 24; - break; - } - } - - bool needsPalette() const { - return (m_header.colormapType == 1); - } - - void writeHeader() { - fputc(m_header.idLength, m_f); - fputc(m_header.colormapType, m_f); - fputc(m_header.imageType, m_f); - fputw(m_header.colormapOrigin, m_f); - fputw(m_header.colormapLength, m_f); - fputc(m_header.colormapDepth, m_f); - fputw(m_header.xOrigin, m_f); - fputw(m_header.yOrigin, m_f); - fputw(m_header.width, m_f); - fputw(m_header.height, m_f); - fputc(m_header.bitsPerPixel, m_f); - fputc(m_header.imageDescriptor, m_f); - } - - void writeFooter() { - const char* tga2_footer = "\0\0\0\0\0\0\0\0TRUEVISION-XFILE.\0"; - fwrite(tga2_footer, 1, 26, m_f); - } - - void writePalette(const Palette* palette) { - ASSERT(palette->size() == m_header.colormapLength); - - for (int i=0; isize(); ++i) { - color_t c = palette->getEntry(i); - fputc(rgba_getb(c), m_f); - fputc(rgba_getg(c), m_f); - fputc(rgba_getr(c), m_f); - if (m_header.colormapDepth == 32) - fputc(rgba_geta(c), m_f); - } - } - - void writeImageData(FileOp* fop, const Image* image) { - auto fput = (m_header.bitsPerPixel == 32 ? &TgaEncoder::fput32: - m_header.bitsPerPixel == 24 ? &TgaEncoder::fput24: - m_header.bitsPerPixel == 16 ? &TgaEncoder::fput16: - m_header.bitsPerPixel == 15 ? &TgaEncoder::fput16: - &TgaEncoder::fput8); - - switch (m_header.imageType) { - - case tga::UncompressedIndexed: { - for (int y=0; yheight(); ++y) { - for (int x=0; xwidth(); ++x) { - color_t c = get_pixel_fast(image, x, y); - fput8(c); - } - fop->setProgress(float(y) / float(image->height())); - } - break; } + break; + case ColorMode::GRAYSCALE: + // TODO if the grayscale is not opaque, we should use RGB, + // this could be done automatically in FileOp in a + // generic way for all formats when FILE_SUPPORT_RGBA is + // available and FILE_SUPPORT_GRAYA isn't. + header.imageType = (compressed ? tga::RleGray: tga::UncompressedGray); + header.bitsPerPixel = 8; + break; + case ColorMode::INDEXED: + ASSERT(palette); - case tga::UncompressedRgb: { - for (int y=0; yheight(); ++y) { - for (int x=0; xwidth(); ++x) { - color_t c = get_pixel_fast(image, x, y); - (this->*fput)(c); - } - fop->setProgress(float(y) / float(image->height())); - } - break; + header.imageType = (compressed ? tga::RleIndexed: tga::UncompressedIndexed); + header.bitsPerPixel = 8; + header.colormapType = 1; + header.colormapLength = palette->size(); + if (palette->hasAlpha()) + header.colormapDepth = 32; + else + header.colormapDepth = 24; + + header.colormap = tga::Colormap(palette->size()); + for (int i=0; isize(); ++i) { + doc::color_t c = palette->getEntry(i); + header.colormap[i] = + tga::rgba(doc::rgba_getr(c), + doc::rgba_getg(c), + doc::rgba_getb(c), + doc::rgba_geta(c)); } - - case tga::UncompressedGray: { - for (int y=0; yheight(); ++y) { - for (int x=0; xwidth(); ++x) { - color_t c = get_pixel_fast(image, x, y); - fput8(graya_getv(c)); - } - fop->setProgress(float(y) / float(image->height())); - } - break; - } - - case tga::RleIndexed: { - for (int y=0; yheight(); ++y) { - writeRleScanline(image, y, fput); - fop->setProgress(float(y) / float(image->height())); - } - break; - } - - case tga::RleRgb: { - ASSERT(m_header.bitsPerPixel == 15 || - m_header.bitsPerPixel == 16 || - m_header.bitsPerPixel == 24 || - m_header.bitsPerPixel == 32); - - for (int y=0; yheight(); ++y) { - writeRleScanline(image, y, fput); - fop->setProgress(float(y) / float(image->height())); - } - break; - } - - case tga::RleGray: { - for (int y=0; yheight(); ++y) { - writeRleScanline(image, y, fput); - fop->setProgress(float(y) / float(image->height())); - } - break; - } - - } + break; } - -private: - template - void writeRleScanline(const Image* image, int y, - void (TgaEncoder::*writePixel)(color_t)) { - int x = 0; - while (x < image->width()) { - int count, offset; - countRepeatedPixels(image, x, y, offset, count); - - // Save a sequence of pixels with different colors - while (offset > 0) { - const int n = std::min(offset, 128); - - fputc(n - 1, m_f); - for (int i=0; i(image, x++, y); - (this->*writePixel)(c); - } - offset -= n; - } - - // Save a sequence of pixels with the same color - while (count*ImageTraits::bytes_per_pixel > 1+ImageTraits::bytes_per_pixel) { - const int n = std::min(count, 128); - const color_t c = get_pixel_fast(image, x, y); - -#if _DEBUG - for (int i=0; i(image, x+i, y) == c); - } -#endif - fputc(0x80 | (n - 1), m_f); - (this->*writePixel)(c); - count -= n; - x += n; - } - } - ASSERT(x == image->width()); - } - - template - void countRepeatedPixels(const Image* image, int x0, int y, - int& offset, int& count) { - for (int x=x0; xwidth(); ) { - color_t p = get_pixel_fast(image, x, y); - - int u = x+1; - for (; uwidth(); ++u) { - color_t q = get_pixel_fast(image, u, y); - if (p != q) - break; - } - - if ((u - x)*ImageTraits::bytes_per_pixel > 1+ImageTraits::bytes_per_pixel) { - offset = x - x0; - count = u - x; - return; - } - - x = u; - } - - offset = image->width() - x0; - count = 0; - } - - void fput8(color_t c) { - fputc(c, m_f); - } - - void fput16(color_t c) { - int r = rgba_getr(c); - int g = rgba_getg(c); - int b = rgba_getb(c); - int a = rgba_geta(c); - uint16_t v = - ((r>>3) << 10) | - ((g>>3) << 5) | - ((b>>3)) | - (m_header.bitsPerPixel == 16 && a >= 128 ? 0x8000: 0); // TODO configurable threshold - fputw(v, m_f); - } - - void fput24(color_t c) { - fputc(rgba_getb(c), m_f); - fputc(rgba_getg(c), m_f); - fputc(rgba_getr(c), m_f); - } - - void fput32(color_t c) { - fputc(rgba_getb(c), m_f); - fputc(rgba_getg(c), m_f); - fputc(rgba_getr(c), m_f); - fputc(rgba_geta(c), m_f); - } - - FILE* m_f; - tga::Header m_header; -}; +} } // anonymous namespace @@ -495,28 +288,32 @@ bool TgaFormat::onSave(FileOp* fop) const Palette* palette = fop->sequenceGetPalette(); FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb")); - FILE* f = handle.get(); + tga::StdioFileInterface finterface(handle.get()); + tga::Encoder encoder(&finterface); + tga::Header header; const auto tgaOptions = std::static_pointer_cast(fop->formatOptions()); - TgaEncoder encoder(f); - - encoder.prepareHeader( - image, palette, + prepare_header( + header, image, palette, // Is alpha channel required? fop->document()->sprite()->isOpaque(), // Compressed by default (tgaOptions ? tgaOptions->compress(): true), // Bits per pixel (0 means "calculate what is best") (tgaOptions ? tgaOptions->bitsPerPixel(): 0)); - encoder.writeHeader(); - if (encoder.needsPalette()) - encoder.writePalette(palette); + encoder.writeHeader(header); - encoder.writeImageData(fop, image); + tga::Image tgaImage; + tgaImage.pixels = image->getPixelAddress(0, 0); + tgaImage.rowstride = image->getRowStrideSize(); + tgaImage.bytesPerPixel = image->getRowStrideSize(1); + + TgaDelegate delegate(fop); + encoder.writeImage(header, tgaImage); encoder.writeFooter(); - if (ferror(f)) { + if (ferror(handle.get())) { fop->setError("Error writing file.\n"); return false; } diff --git a/src/tga b/src/tga index d821575f5..e3f86799a 160000 --- a/src/tga +++ b/src/tga @@ -1 +1 @@ -Subproject commit d821575f5284d56429909fc4c69579562d06a4f6 +Subproject commit e3f86799a6bc45e106d7f7c7f322dee81893ba3e