mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-23 00:40:04 +00:00
Use tga library to encode files
This commit is contained in:
parent
538dc9e6aa
commit
780133be6f
@ -70,9 +70,9 @@ FileFormat* CreateTgaFormat()
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class TgaDecoderDelegate : public tga::DecoderDelegate {
|
class TgaDelegate : public tga::Delegate {
|
||||||
public:
|
public:
|
||||||
TgaDecoderDelegate(FileOp* fop) : m_fop(fop) { }
|
TgaDelegate(FileOp* fop) : m_fop(fop) { }
|
||||||
bool notifyProgress(double progress) override {
|
bool notifyProgress(double progress) override {
|
||||||
m_fop->setProgress(progress);
|
m_fop->setProgress(progress);
|
||||||
return !m_fop->isStop();
|
return !m_fop->isStop();
|
||||||
@ -123,10 +123,8 @@ bool get_image_spec(const tga::Header& header, ImageSpec& spec)
|
|||||||
bool TgaFormat::onLoad(FileOp* fop)
|
bool TgaFormat::onLoad(FileOp* fop)
|
||||||
{
|
{
|
||||||
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
|
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
|
||||||
FILE* f = handle.get();
|
tga::StdioFileInterface finterface(handle.get());
|
||||||
tga::StdioFileInterface finterface(f);
|
|
||||||
tga::Decoder decoder(&finterface);
|
tga::Decoder decoder(&finterface);
|
||||||
|
|
||||||
tga::Header header;
|
tga::Header header;
|
||||||
if (!decoder.readHeader(header)) {
|
if (!decoder.readHeader(header)) {
|
||||||
fop->setError("Invalid TGA header\n");
|
fop->setError("Invalid TGA header\n");
|
||||||
@ -175,7 +173,7 @@ bool TgaFormat::onLoad(FileOp* fop)
|
|||||||
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
||||||
|
|
||||||
// Read image
|
// Read image
|
||||||
TgaDecoderDelegate delegate(fop);
|
TgaDelegate delegate(fop);
|
||||||
if (!decoder.readImage(header, tgaImage, &delegate)) {
|
if (!decoder.readImage(header, tgaImage, &delegate)) {
|
||||||
fop->setError("Error loading image data from TGA file.\n");
|
fop->setError("Error loading image data from TGA file.\n");
|
||||||
return false;
|
return false;
|
||||||
@ -202,7 +200,7 @@ bool TgaFormat::onLoad(FileOp* fop)
|
|||||||
opts->compress(header.isRle());
|
opts->compress(header.isRle());
|
||||||
fop->setLoadedFormatOptions(opts);
|
fop->setLoadedFormatOptions(opts);
|
||||||
|
|
||||||
if (ferror(f)) {
|
if (ferror(handle.get())) {
|
||||||
fop->setError("Error reading file.\n");
|
fop->setError("Error reading file.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -215,277 +213,72 @@ bool TgaFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class TgaEncoder {
|
void prepare_header(tga::Header& header,
|
||||||
public:
|
const doc::Image* image,
|
||||||
TgaEncoder(FILE* f)
|
const doc::Palette* palette,
|
||||||
: m_f(f) {
|
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,
|
switch (image->colorMode()) {
|
||||||
const Palette* palette,
|
case ColorMode::RGB:
|
||||||
const bool isOpaque,
|
header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb);
|
||||||
const bool compressed,
|
header.bitsPerPixel = (bitsPerPixel > 8 ?
|
||||||
int bitsPerPixel) {
|
bitsPerPixel:
|
||||||
m_header.idLength = 0;
|
(isOpaque ? 24: 32));
|
||||||
m_header.colormapType = 0;
|
if (!isOpaque) {
|
||||||
m_header.imageType = tga::NoImage;
|
switch (header.bitsPerPixel) {
|
||||||
m_header.colormapOrigin = 0;
|
case 16: header.imageDescriptor |= 1; break;
|
||||||
m_header.colormapLength = 0;
|
case 32: header.imageDescriptor |= 8; break;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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; i<palette->size(); ++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; y<image->height(); ++y) {
|
|
||||||
for (int x=0; x<image->width(); ++x) {
|
|
||||||
color_t c = get_pixel_fast<IndexedTraits>(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: {
|
header.imageType = (compressed ? tga::RleIndexed: tga::UncompressedIndexed);
|
||||||
for (int y=0; y<image->height(); ++y) {
|
header.bitsPerPixel = 8;
|
||||||
for (int x=0; x<image->width(); ++x) {
|
header.colormapType = 1;
|
||||||
color_t c = get_pixel_fast<RgbTraits>(image, x, y);
|
header.colormapLength = palette->size();
|
||||||
(this->*fput)(c);
|
if (palette->hasAlpha())
|
||||||
}
|
header.colormapDepth = 32;
|
||||||
fop->setProgress(float(y) / float(image->height()));
|
else
|
||||||
}
|
header.colormapDepth = 24;
|
||||||
break;
|
|
||||||
|
header.colormap = tga::Colormap(palette->size());
|
||||||
|
for (int i=0; i<palette->size(); ++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));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case tga::UncompressedGray: {
|
|
||||||
for (int y=0; y<image->height(); ++y) {
|
|
||||||
for (int x=0; x<image->width(); ++x) {
|
|
||||||
color_t c = get_pixel_fast<GrayscaleTraits>(image, x, y);
|
|
||||||
fput8(graya_getv(c));
|
|
||||||
}
|
|
||||||
fop->setProgress(float(y) / float(image->height()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case tga::RleIndexed: {
|
|
||||||
for (int y=0; y<image->height(); ++y) {
|
|
||||||
writeRleScanline<IndexedTraits>(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; y<image->height(); ++y) {
|
|
||||||
writeRleScanline<RgbTraits>(image, y, fput);
|
|
||||||
fop->setProgress(float(y) / float(image->height()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case tga::RleGray: {
|
|
||||||
for (int y=0; y<image->height(); ++y) {
|
|
||||||
writeRleScanline<GrayscaleTraits>(image, y, fput);
|
|
||||||
fop->setProgress(float(y) / float(image->height()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private:
|
|
||||||
template<typename ImageTraits>
|
|
||||||
void writeRleScanline(const Image* image, int y,
|
|
||||||
void (TgaEncoder::*writePixel)(color_t)) {
|
|
||||||
int x = 0;
|
|
||||||
while (x < image->width()) {
|
|
||||||
int count, offset;
|
|
||||||
countRepeatedPixels<ImageTraits>(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<n; ++i) {
|
|
||||||
const color_t c = get_pixel_fast<ImageTraits>(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<ImageTraits>(image, x, y);
|
|
||||||
|
|
||||||
#if _DEBUG
|
|
||||||
for (int i=0; i<n; ++i) {
|
|
||||||
ASSERT(get_pixel_fast<ImageTraits>(image, x+i, y) == c);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
fputc(0x80 | (n - 1), m_f);
|
|
||||||
(this->*writePixel)(c);
|
|
||||||
count -= n;
|
|
||||||
x += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT(x == image->width());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename ImageTraits>
|
|
||||||
void countRepeatedPixels(const Image* image, int x0, int y,
|
|
||||||
int& offset, int& count) {
|
|
||||||
for (int x=x0; x<image->width(); ) {
|
|
||||||
color_t p = get_pixel_fast<ImageTraits>(image, x, y);
|
|
||||||
|
|
||||||
int u = x+1;
|
|
||||||
for (; u<image->width(); ++u) {
|
|
||||||
color_t q = get_pixel_fast<ImageTraits>(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
|
} // anonymous namespace
|
||||||
|
|
||||||
@ -495,28 +288,32 @@ bool TgaFormat::onSave(FileOp* fop)
|
|||||||
const Palette* palette = fop->sequenceGetPalette();
|
const Palette* palette = fop->sequenceGetPalette();
|
||||||
|
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
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<TgaOptions>(fop->formatOptions());
|
const auto tgaOptions = std::static_pointer_cast<TgaOptions>(fop->formatOptions());
|
||||||
TgaEncoder encoder(f);
|
prepare_header(
|
||||||
|
header, image, palette,
|
||||||
encoder.prepareHeader(
|
|
||||||
image, palette,
|
|
||||||
// Is alpha channel required?
|
// Is alpha channel required?
|
||||||
fop->document()->sprite()->isOpaque(),
|
fop->document()->sprite()->isOpaque(),
|
||||||
// Compressed by default
|
// Compressed by default
|
||||||
(tgaOptions ? tgaOptions->compress(): true),
|
(tgaOptions ? tgaOptions->compress(): true),
|
||||||
// Bits per pixel (0 means "calculate what is best")
|
// Bits per pixel (0 means "calculate what is best")
|
||||||
(tgaOptions ? tgaOptions->bitsPerPixel(): 0));
|
(tgaOptions ? tgaOptions->bitsPerPixel(): 0));
|
||||||
encoder.writeHeader();
|
|
||||||
|
|
||||||
if (encoder.needsPalette())
|
encoder.writeHeader(header);
|
||||||
encoder.writePalette(palette);
|
|
||||||
|
|
||||||
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();
|
encoder.writeFooter();
|
||||||
|
|
||||||
if (ferror(f)) {
|
if (ferror(handle.get())) {
|
||||||
fop->setError("Error writing file.\n");
|
fop->setError("Error writing file.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
2
src/tga
2
src/tga
@ -1 +1 @@
|
|||||||
Subproject commit d821575f5284d56429909fc4c69579562d06a4f6
|
Subproject commit e3f86799a6bc45e106d7f7c7f322dee81893ba3e
|
Loading…
x
Reference in New Issue
Block a user