mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-16 10:20:50 +00:00
Add a semi-transparent pixel (alpha=254) when export png for Twitter
This commit is contained in:
parent
56ec9f113c
commit
b015fee8e0
@ -20,6 +20,7 @@
|
||||
#include "app/document_undo.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/gif_format.h"
|
||||
#include "app/file/png_format.h"
|
||||
#include "app/file_selector.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/job.h"
|
||||
@ -422,7 +423,9 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
convert_anidir_to_string(aniDirValue), // New value
|
||||
m_aniDir); // Restore old value
|
||||
|
||||
GifEncoderDurationFix fix(isForTwitter);
|
||||
// TODO This should be set as options for the specific encoder
|
||||
GifEncoderDurationFix fixGif(isForTwitter);
|
||||
PngEncoderOneAlphaPixel fixPng(isForTwitter);
|
||||
|
||||
saveDocumentInBackground(
|
||||
context, doc, outputFilename, false);
|
||||
|
@ -103,6 +103,7 @@ FileFormat* CreateGifFormat()
|
||||
static int interlaced_offset[] = { 0, 4, 2, 1 };
|
||||
static int interlaced_jumps[] = { 8, 8, 4, 2 };
|
||||
|
||||
// TODO this should be part of a GifEncoder instance
|
||||
// True if the GifEncoder should save the animation for Twitter:
|
||||
// * Frames duration >= 2, and
|
||||
// * Last frame 1/4 of its duration
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/file/png_format.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "doc/doc.h"
|
||||
|
||||
@ -67,6 +68,19 @@ static void report_png_error(png_structp png_ptr, png_const_charp error)
|
||||
((FileOp*)png_get_error_ptr(png_ptr))->setError("libpng: %s\n", error);
|
||||
}
|
||||
|
||||
// TODO this should be part of an png encoder instance
|
||||
static bool fix_one_alpha_pixel = false;
|
||||
|
||||
PngEncoderOneAlphaPixel::PngEncoderOneAlphaPixel(bool state)
|
||||
{
|
||||
fix_one_alpha_pixel = state;
|
||||
}
|
||||
|
||||
PngEncoderOneAlphaPixel::~PngEncoderOneAlphaPixel()
|
||||
{
|
||||
fix_one_alpha_pixel = false;
|
||||
}
|
||||
|
||||
bool PngFormat::onLoad(FileOp* fop)
|
||||
{
|
||||
png_uint_32 width, height, y;
|
||||
@ -352,81 +366,66 @@ bool PngFormat::onLoad(FileOp* fop)
|
||||
#ifdef ENABLE_SAVE
|
||||
bool PngFormat::onSave(FileOp* fop)
|
||||
{
|
||||
const Image* image = fop->sequenceImage();
|
||||
png_uint_32 width, height, y;
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
png_colorp palette = NULL;
|
||||
png_colorp palette = nullptr;
|
||||
png_bytep row_pointer;
|
||||
int color_type = 0;
|
||||
int pass, number_passes;
|
||||
|
||||
/* open the file */
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
FILE* fp = handle.get();
|
||||
|
||||
/* Create and initialize the png_struct with the desired error handler
|
||||
* functions. If you want to use the default stderr and longjump method,
|
||||
* you can supply NULL for the last three parameters. We also check that
|
||||
* the library version is compatible with the one used at compile time,
|
||||
* in case we are using dynamically linked libraries. REQUIRED.
|
||||
*/
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)fop,
|
||||
report_png_error, report_png_error);
|
||||
if (png_ptr == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Allocate/initialize the image information data. REQUIRED */
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (info_ptr == NULL) {
|
||||
png_destroy_write_struct(&png_ptr, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set error handling. REQUIRED if you aren't supplying your own
|
||||
* error handling functions in the png_create_write_struct() call.
|
||||
*/
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
/* If we get here, we had a problem reading the file */
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* set up the output control if you are using standard C streams */
|
||||
png_init_io(png_ptr, fp);
|
||||
|
||||
/* Set the image information here. Width and height are up to 2^31,
|
||||
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
|
||||
* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
|
||||
* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
|
||||
* or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
|
||||
* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
|
||||
* currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
|
||||
*/
|
||||
width = image->width();
|
||||
height = image->height();
|
||||
|
||||
const Image* image = fop->sequenceImage();
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
color_type = fop->document()->sprite()->needAlpha() ?
|
||||
PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
PNG_COLOR_TYPE_RGB;
|
||||
color_type =
|
||||
(fop->document()->sprite()->needAlpha() ||
|
||||
fix_one_alpha_pixel ?
|
||||
PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
PNG_COLOR_TYPE_RGB);
|
||||
break;
|
||||
case IMAGE_GRAYSCALE:
|
||||
color_type = fop->document()->sprite()->needAlpha() ?
|
||||
PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
PNG_COLOR_TYPE_GRAY;
|
||||
color_type =
|
||||
(fop->document()->sprite()->needAlpha() ||
|
||||
fix_one_alpha_pixel ?
|
||||
PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
PNG_COLOR_TYPE_GRAY);
|
||||
break;
|
||||
case IMAGE_INDEXED:
|
||||
color_type = PNG_COLOR_TYPE_PALETTE;
|
||||
if (fix_one_alpha_pixel)
|
||||
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
else
|
||||
color_type = PNG_COLOR_TYPE_PALETTE;
|
||||
break;
|
||||
}
|
||||
|
||||
const png_uint_32 width = image->width();
|
||||
const png_uint_32 height = image->height();
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
|
||||
if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
int c, r, g, b;
|
||||
int pal_size = fop->sequenceGetNColors();
|
||||
pal_size = MID(1, pal_size, PNG_MAX_PALETTE_LENGTH);
|
||||
@ -472,111 +471,129 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
png_free(png_ptr, trans);
|
||||
}
|
||||
|
||||
/* Write the file header information. */
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
/* pack pixels into bytes */
|
||||
png_set_packing(png_ptr);
|
||||
|
||||
/* non-interlaced */
|
||||
number_passes = 1;
|
||||
|
||||
row_pointer = (png_bytep)png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
|
||||
|
||||
/* The number of passes is either 1 for non-interlaced images,
|
||||
* or 7 for interlaced images.
|
||||
*/
|
||||
for (pass = 0; pass < number_passes; pass++) {
|
||||
/* If you are only writing one row at a time, this works */
|
||||
for (y = 0; y < height; y++) {
|
||||
/* RGB_ALPHA */
|
||||
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGB_ALPHA) {
|
||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
||||
uint8_t* dst_address = row_pointer;
|
||||
unsigned int x, c;
|
||||
for (png_uint_32 y=0; y<height; ++y) {
|
||||
uint8_t* dst_address = row_pointer;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGB_ALPHA) {
|
||||
unsigned int x, c, a;
|
||||
bool opaque = true;
|
||||
|
||||
if (image->pixelFormat() == IMAGE_RGB) {
|
||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
||||
|
||||
for (x=0; x<width; ++x) {
|
||||
c = *(src_address++);
|
||||
a = rgba_geta(c);
|
||||
|
||||
if (opaque) {
|
||||
if (a < 255)
|
||||
opaque = false;
|
||||
else if (fix_one_alpha_pixel && x == width-1 && y == height-1)
|
||||
a = 254;
|
||||
}
|
||||
|
||||
*(dst_address++) = rgba_getr(c);
|
||||
*(dst_address++) = rgba_getg(c);
|
||||
*(dst_address++) = rgba_getb(c);
|
||||
*(dst_address++) = rgba_geta(c);
|
||||
*(dst_address++) = a;
|
||||
}
|
||||
}
|
||||
/* RGB */
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGB) {
|
||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
||||
uint8_t* dst_address = row_pointer;
|
||||
unsigned int x, c;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = rgba_getr(c);
|
||||
*(dst_address++) = rgba_getg(c);
|
||||
*(dst_address++) = rgba_getb(c);
|
||||
}
|
||||
}
|
||||
/* GRAY_ALPHA */
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
||||
uint8_t* dst_address = row_pointer;
|
||||
unsigned int x, c;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = graya_getv(c);
|
||||
*(dst_address++) = graya_geta(c);
|
||||
}
|
||||
}
|
||||
/* GRAY */
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY) {
|
||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
||||
uint8_t* dst_address = row_pointer;
|
||||
unsigned int x, c;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = graya_getv(c);
|
||||
}
|
||||
}
|
||||
/* PALETTE */
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) {
|
||||
// In case that we are converting an indexed image to RGB just
|
||||
// to convert one pixel with alpha=254.
|
||||
else if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
||||
uint8_t* dst_address = row_pointer;
|
||||
unsigned int x;
|
||||
unsigned int x, c;
|
||||
int r, g, b, a;
|
||||
bool opaque = true;
|
||||
|
||||
for (x=0; x<width; x++)
|
||||
*(dst_address++) = *(src_address++);
|
||||
for (x=0; x<width; ++x) {
|
||||
c = *(src_address++);
|
||||
fop->sequenceGetColor(c, &r, &g, &b);
|
||||
fop->sequenceGetAlpha(c, &a);
|
||||
|
||||
if (opaque) {
|
||||
if (a < 255)
|
||||
opaque = false;
|
||||
else if (fix_one_alpha_pixel && x == width-1 && y == height-1)
|
||||
a = 254;
|
||||
}
|
||||
|
||||
*(dst_address++) = r;
|
||||
*(dst_address++) = g;
|
||||
*(dst_address++) = b;
|
||||
*(dst_address++) = a;
|
||||
}
|
||||
}
|
||||
|
||||
/* write the line */
|
||||
png_write_rows(png_ptr, &row_pointer, 1);
|
||||
|
||||
fop->setProgress(
|
||||
(double)((double)pass + (double)(y+1) / (double)(height))
|
||||
/ (double)number_passes);
|
||||
}
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGB) {
|
||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
||||
unsigned int x, c;
|
||||
|
||||
for (x=0; x<width; ++x) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = rgba_getr(c);
|
||||
*(dst_address++) = rgba_getg(c);
|
||||
*(dst_address++) = rgba_getb(c);
|
||||
}
|
||||
}
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
||||
unsigned int x, c, a;
|
||||
bool opaque = true;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
c = *(src_address++);
|
||||
a = graya_geta(c);
|
||||
|
||||
if (opaque) {
|
||||
if (a < 255)
|
||||
opaque = false;
|
||||
else if (fix_one_alpha_pixel && x == width-1 && y == height-1)
|
||||
a = 254;
|
||||
}
|
||||
|
||||
*(dst_address++) = graya_getv(c);
|
||||
*(dst_address++) = a;
|
||||
}
|
||||
}
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY) {
|
||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
||||
unsigned int x, c;
|
||||
|
||||
for (x=0; x<width; ++x) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = graya_getv(c);
|
||||
}
|
||||
}
|
||||
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) {
|
||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
||||
unsigned int x;
|
||||
|
||||
for (x=0; x<width; ++x)
|
||||
*(dst_address++) = *(src_address++);
|
||||
}
|
||||
|
||||
png_write_rows(png_ptr, &row_pointer, 1);
|
||||
|
||||
fop->setProgress(
|
||||
(double)((double)pass + (double)(y+1) / (double)(height))
|
||||
/ (double)number_passes);
|
||||
}
|
||||
|
||||
png_free(png_ptr, row_pointer);
|
||||
|
||||
/* It is REQUIRED to call this to finish writing the rest of the file */
|
||||
png_write_end(png_ptr, info_ptr);
|
||||
|
||||
/* If you png_malloced a palette, free it here (don't free info_ptr->palette,
|
||||
as recommended in versions 1.0.5m and earlier of this example; if
|
||||
libpng mallocs info_ptr->palette, libpng will free it). If you
|
||||
allocated it with malloc() instead of png_malloc(), use free() instead
|
||||
of png_free(). */
|
||||
if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
png_free(png_ptr, palette);
|
||||
palette = NULL;
|
||||
palette = nullptr;
|
||||
}
|
||||
|
||||
/* clean up after the write, and free any memory allocated */
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
/* all right */
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
24
src/app/file/png_format.h
Normal file
24
src/app/file/png_format.h
Normal file
@ -0,0 +1,24 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_FILE_PNG_FORMAT_H_INCLUDED
|
||||
#define APP_FILE_PNG_FORMAT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
namespace app {
|
||||
|
||||
// This can be used to save opaque png files with one pixel with
|
||||
// alpha=254, which is needed by Twitter to publish png images and
|
||||
// avoid automatic conversion to jpg files.
|
||||
class PngEncoderOneAlphaPixel {
|
||||
public:
|
||||
PngEncoderOneAlphaPixel(bool state);
|
||||
~PngEncoderOneAlphaPixel();
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user