[aseprite] 38/51: Add support for Pixly file format (#1177)
Tobias Hansen
thansen at moszumanska.debian.org
Mon Jul 11 21:35:18 UTC 2016
This is an automated email from the git hooks/post-receive script.
thansen pushed a commit to branch master
in repository aseprite.
commit 7b4a1ec4af13763c26df4e57d27fdc473b597c11
Author: Carlo "zED" Caputo <carlo.caputo at gmail.com>
Date: Mon Jul 4 12:06:27 2016 -0300
Add support for Pixly file format (#1177)
New Pixly .anim format decoder/encoder based on png decoder/encoder.
---
src/app/CMakeLists.txt | 1 +
src/app/file/file_formats_manager.cpp | 2 +
src/app/file/pixly_format.cpp | 525 ++++++++++++++++++++++++++++++++++
src/base/path.cpp | 24 ++
src/base/path.h | 3 +
5 files changed, 555 insertions(+)
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 58750f8..7132909 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -83,6 +83,7 @@ set(file_formats
file/jpeg_format.cpp
file/pcx_format.cpp
file/png_format.cpp
+ file/pixly_format.cpp
file/tga_format.cpp)
if(WITH_WEBP_SUPPORT)
list(APPEND file_formats file/webp_format.cpp)
diff --git a/src/app/file/file_formats_manager.cpp b/src/app/file/file_formats_manager.cpp
index ec0b207..27822b9 100644
--- a/src/app/file/file_formats_manager.cpp
+++ b/src/app/file/file_formats_manager.cpp
@@ -21,6 +21,7 @@
namespace app {
extern FileFormat* CreateAseFormat();
+extern FileFormat* CreatePixlyFormat();
extern FileFormat* CreateBmpFormat();
extern FileFormat* CreateFliFormat();
extern FileFormat* CreateGifFormat();
@@ -63,6 +64,7 @@ void FileFormatsManager::registerAllFormats()
{
// The first format is the default image format in FileSelector
registerFormat(CreateAseFormat());
+ registerFormat(CreatePixlyFormat());
registerFormat(CreateBmpFormat());
registerFormat(CreateFliFormat());
registerFormat(CreateGifFormat());
diff --git a/src/app/file/pixly_format.cpp b/src/app/file/pixly_format.cpp
new file mode 100644
index 0000000..b14e323
--- /dev/null
+++ b/src/app/file/pixly_format.cpp
@@ -0,0 +1,525 @@
+// Aseprite
+// Copyright (C) 2016 Carlo "zED" Caputo
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// Based on the code of David Capello
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/document.h"
+#include "app/file/file.h"
+#include "app/file/file_format.h"
+#include "app/xml_document.h"
+#include "base/file_handle.h"
+#include "base/convert_to.h"
+#include "base/path.h"
+#include "doc/doc.h"
+
+#include <cmath>
+#include <cctype>
+
+#include "png.h"
+
+namespace app {
+
+using namespace base;
+
+class PixlyFormat : public FileFormat {
+ const char* onGetName() const override { return "anim"; }
+ const char* onGetExtensions() const override { return "anim"; }
+ int onGetFlags() const override {
+ return
+ FILE_SUPPORT_LOAD |
+ FILE_SUPPORT_SAVE |
+ FILE_SUPPORT_RGB |
+ FILE_SUPPORT_RGBA |
+ FILE_SUPPORT_LAYERS |
+ FILE_SUPPORT_FRAMES |
+ FILE_SUPPORT_BIG_PALETTES |
+ FILE_SUPPORT_PALETTE_WITH_ALPHA;
+ }
+
+ bool onLoad(FileOp* fop) override;
+#ifdef ENABLE_SAVE
+ bool onSave(FileOp* fop) override;
+#endif
+};
+
+FileFormat* CreatePixlyFormat()
+{
+ return new PixlyFormat;
+}
+
+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);
+}
+
+template<typename Any> static Any* check(Any* a, Any* alt = NULL) {
+ if(a == NULL) {
+ if(alt == NULL) {
+ throw Exception("bad structure");
+ } else {
+ return alt;
+ }
+ } else {
+ return a;
+ }
+}
+
+template<typename Number> static Number check_number(const char* c_str) {
+ if(c_str == NULL) {
+ throw Exception("value not found");
+ } else {
+ std::string str = c_str;
+ if(str.empty()) {
+ throw Exception("value empty");
+ }
+ std::string::const_iterator it = str.begin();
+ while (it != str.end() && (std::isdigit(*it) || *it == '.')) ++it;
+ if(it != str.end()) {
+ throw Exception("value not a number");
+ }
+ return base::convert_to<Number>(str);
+ }
+}
+
+
+bool PixlyFormat::onLoad(FileOp* fop)
+{
+ png_uint_32 width, height, y;
+ unsigned int sig_read = 0;
+ png_structp png_ptr;
+ png_infop info_ptr;
+ int bit_depth, color_type, interlace_type;
+ int pass, number_passes;
+ png_bytepp rows_pointer;
+ PixelFormat pixelFormat;
+
+ FileHandle handle(open_file_with_exception(base::replace_extension(fop->filename(),"png"), "rb"));
+ 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 supply the
+ * the compiler header file version, so that we know if the application
+ * was compiled with a compatible version of the library
+ */
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)fop,
+ report_png_error, report_png_error);
+ if (png_ptr == NULL) {
+ fop->setError("png_create_read_struct\n");
+ return false;
+ }
+
+ /* Allocate/initialize the memory for image information. */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ fop->setError("png_create_info_struct\n");
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
+ return false;
+ }
+
+ /* Set error handling if you are using the setjmp/longjmp method (this is
+ * the normal method of doing things with libpng).
+ */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ fop->setError("Error reading PNG file\n");
+ /* Free all of the memory associated with the png_ptr and info_ptr */
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ /* If we get here, we had a problem reading the file */
+ return false;
+ }
+
+ /* Set up the input control if you are using standard C streams */
+ png_init_io(png_ptr, fp);
+
+ /* If we have already read some of the signature */
+ png_set_sig_bytes(png_ptr, sig_read);
+
+ /* The call to png_read_info() gives us all of the information from the
+ * PNG file before the first IDAT (image data chunk).
+ */
+ png_read_info(png_ptr, info_ptr);
+
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_type, NULL, NULL);
+
+
+ /* Set up the data transformations you want. Note that these are all
+ * optional. Only call them if you want/need them. Many of the
+ * transformations only work on specific types of images, and many
+ * are mutually exclusive.
+ */
+
+ /* tell libpng to strip 16 bit/color files down to 8 bits/color */
+ png_set_strip_16(png_ptr);
+
+ /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
+ * byte into separate bytes (useful for paletted and grayscale images).
+ */
+ png_set_packing(png_ptr);
+
+ /* Turn on interlace handling. REQUIRED if you are not using
+ * png_read_image(). To see how to handle interlacing passes,
+ * see the png_read_row() method below:
+ */
+ number_passes = png_set_interlace_handling(png_ptr);
+
+ /* Optional call to gamma correct and add the background to the palette
+ * and update info structure.
+ */
+ png_read_update_info(png_ptr, info_ptr);
+
+ /* create the output image */
+ switch (png_get_color_type(png_ptr, info_ptr)) {
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ fop->sequenceSetHasAlpha(true);
+ pixelFormat = IMAGE_RGB;
+ break;
+
+ default:
+ fop->setError("Pixly loader requires a RGBA PNG\n)");
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ return false;
+ }
+
+ int imageWidth = png_get_image_width(png_ptr, info_ptr);
+ int imageHeight = png_get_image_height(png_ptr, info_ptr);
+
+ // Allocate the memory to hold the image using the fields of info_ptr.
+ rows_pointer = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
+ for (y = 0; y < height; y++)
+ rows_pointer[y] = (png_bytep)png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
+
+ for (pass = 0; pass < number_passes; pass++) {
+ for (y = 0; y < height; y++) {
+ png_read_rows(png_ptr, rows_pointer+y, nullptr, 1);
+
+ fop->setProgress(
+ 0.5 * ((double)((double)pass + (double)(y+1) / (double)(height))
+ / (double)number_passes));
+
+ if (fop->isStop())
+ break;
+ }
+ }
+
+ bool success = true;
+ try {
+ XmlDocumentRef doc = open_xml(fop->filename());
+ TiXmlHandle xml(doc.get());
+ fop->setProgress(0.75);
+
+ TiXmlElement* xmlAnim = check(xml.FirstChild("PixlyAnimation").ToElement());
+ double version = check_number<double>(xmlAnim->Attribute("version"));
+ if(version < 1.5) {
+ throw Exception("version 1.5 or above required");
+ }
+
+ TiXmlElement* xmlInfo = check(xmlAnim->FirstChild("Info"))->ToElement();
+
+ int layerCount = check_number<int>(xmlInfo->Attribute("layerCount"));
+ int frameWidth = check_number<int>(xmlInfo->Attribute("frameWidth"));
+ int frameHeight = check_number<int>(xmlInfo->Attribute("frameHeight"));
+
+ UniquePtr<Sprite> sprite(new Sprite(IMAGE_RGB, frameWidth, frameHeight, 0));
+
+ TiXmlElement* xmlFrames = check(xmlAnim->FirstChild("Frames"))->ToElement();
+ int imageCount = check_number<int>(xmlFrames->Attribute("length"));
+
+ if(layerCount <= 0 || imageCount <= 0) {
+ throw Exception("No cels found");
+ }
+
+ int frameCount = imageCount / layerCount;
+ sprite->setTotalFrames(frame_t(frameCount));
+ sprite->setDurationForAllFrames(200);
+
+ for(int i=0; i<layerCount; i++) {
+ sprite->folder()->addLayer(new LayerImage(sprite));
+ }
+
+ std::vector<int> visible(layerCount, 0);
+
+ TiXmlElement* xmlFrame = check(xmlFrames->FirstChild("Frame"))->ToElement();
+ while (xmlFrame) {
+ TiXmlElement* xmlRegion = check(xmlFrame->FirstChild("Region"))->ToElement();
+ TiXmlElement* xmlIndex = check(xmlFrame->FirstChild("Index"))->ToElement();
+
+ int index = check_number<int>(xmlIndex->Attribute("linear"));
+ frame_t frame(index / layerCount);
+ LayerIndex layer_index(index % layerCount);
+ Layer *layer = sprite->indexToLayer(layer_index);
+
+ const char * duration = xmlFrame->Attribute("duration");
+ if(duration) {
+ sprite->setFrameDuration(frame, base::convert_to<int>(std::string(duration)));
+ }
+
+ visible[(int)layer_index] += (int)(std::string(check(xmlFrame->Attribute("visible"),"false")) == "true");
+
+ int x0 = check_number<int>(xmlRegion->Attribute("x"));
+ int y0 = check_number<int>(xmlRegion->Attribute("y")); // inverted
+
+ if(y0 < 0 || y0 + frameHeight > imageHeight || x0 < 0 || x0 + frameWidth > imageWidth) {
+ throw Exception("looking for cels outside the bounds of the PNG");
+ }
+
+ base::UniquePtr<Cel> cel;
+ ImageRef image(Image::create(pixelFormat, frameWidth, frameHeight));
+
+ // Convert rows_pointer into the doc::Image
+ for (int y = 0; y < frameHeight; y++) {
+ // RGB_ALPHA
+ uint8_t* src_address = rows_pointer[imageHeight-1 - y0 - (frameHeight-1) + y] + (x0 * 4);
+ uint32_t* dst_address = (uint32_t*)image->getPixelAddress(0, y);
+ unsigned int r, g, b, a;
+
+ for (int x=0; x<frameWidth; x++) {
+ r = *(src_address++);
+ g = *(src_address++);
+ b = *(src_address++);
+ a = *(src_address++);
+ *(dst_address++) = rgba(r, g, b, a);
+ }
+ }
+
+ cel.reset(new Cel(frame, image));
+ static_cast<LayerImage*>(layer)->addCel(cel);
+ cel.release();
+
+ xmlFrame = xmlFrame->NextSiblingElement();
+ fop->setProgress(0.75 + 0.25 * ((float)(index+1) / (float)imageCount));
+ }
+
+ for(int i=0; i<layerCount; i++) {
+ LayerIndex layer_index(i);
+ Layer *layer = sprite->indexToLayer(layer_index);
+ layer->setVisible(visible[i] > frameCount/2);
+ }
+
+ fop->createDocument(sprite);
+ sprite.release();
+ }
+ catch(Exception &e) {
+ fop->setError((std::string("Pixly file format: ")+std::string(e.what())+"\n").c_str());
+ success = false;
+ }
+
+ for (y = 0; y < height; y++) {
+ png_free(png_ptr, rows_pointer[y]);
+ }
+ png_free(png_ptr, rows_pointer);
+
+ // Clean up after the read, and free any memory allocated
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ return success;
+}
+
+#ifdef ENABLE_SAVE
+bool PixlyFormat::onSave(FileOp* fop)
+{
+ const Sprite* sprite = fop->document()->sprite();
+
+ auto it = sprite->folder()->getLayerBegin(),
+ end = sprite->folder()->getLayerEnd();
+ for (; it != end; ++it) { // layers
+ Layer *layer = *it;
+ if (!layer->isImage()) {
+ fop->setError("Pixly .anim file format does not support layer folders\n");
+ return false;
+ }
+ }
+
+ int width, height, y;
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_colorp palette = NULL;
+ png_bytepp rows_pointer;
+ int color_type = 0;
+
+ /* open the file */
+ FileHandle xml_handle(open_file_with_exception(fop->filename(), "wb"));
+ FILE* xml_fp = xml_handle.get();
+
+ FileHandle handle(open_file_with_exception(base::replace_extension(fop->filename(),"png"), "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
+ */
+
+ int frameCount = sprite->totalFrames();
+ int layerCount = sprite->folder()->getLayersCount();
+ int imageCount = frameCount * layerCount;
+
+ int frameWidth = sprite->width();
+ int frameHeight = sprite->height();
+
+ int squareSide = (int)ceil(sqrt(imageCount));
+
+ width = squareSide * frameWidth;
+ height = squareSide * frameHeight;
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ /* Write the file header information. */
+ png_write_info(png_ptr, info_ptr);
+
+ /* pack pixels into bytes */
+ png_set_packing(png_ptr);
+
+ rows_pointer = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
+ for (y = 0; y < height; y++) {
+ size_t size = png_get_rowbytes(png_ptr, info_ptr);
+ rows_pointer[y] = (png_bytep)png_malloc(png_ptr, size);
+ memset(rows_pointer[y], 0, size);
+ fop->setProgress(0.1 * (double)(y+1) / (double)height);
+ }
+
+ // XXX beware the required typo on Pixly xml: "totalCollumns" (sic)
+ fprintf(xml_fp,
+ "<PixlyAnimation version=\"1.5\">\n"
+ "\t<Info "
+ "sheetWidth=\"%d\" sheetHeight=\"%d\" "
+ "totalCollumns=\"%d\" totalRows=\"%d\" "
+ "frameWidth=\"%d\" frameHeight=\"%d\" "
+ "layerCount=\"%d\"/>\n"
+ "\t<Frames length=\"%d\">\n",
+ width, height,
+ squareSide, squareSide,
+ frameWidth, frameHeight,
+ layerCount, imageCount
+ );
+
+
+ int index = 0;
+ for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
+ auto it = sprite->folder()->getLayerBegin(),
+ end = sprite->folder()->getLayerEnd();
+ for (; it != end; ++it, ++index) { // layers
+ Layer *layer = *it;
+
+ int col = index % squareSide;
+ int row = index / squareSide;
+
+ int x0 = col * frameWidth;
+ int y0 = row * frameHeight; // inverted
+
+ int duration = sprite->frameDuration(frame);
+
+ // XXX beware the required typo on Pixly xml: "collumn" (sic)
+ fprintf(xml_fp,
+ "\t\t<Frame duration=\"%d\" visible=\"%s\">\n"
+ "\t\t\t<Region x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"/>\n"
+ "\t\t\t<Index linear=\"%d\" collumn=\"%d\" row=\"%d\"/>\n"
+ "\t\t</Frame>\n",
+ duration, layer->isVisible() ? "true" : "false",
+ x0, y0, frameWidth, frameHeight,
+ index, col, row
+ );
+
+ const Cel* cel = layer->cel(frame);
+ if (cel) {
+ const Image* image = cel->image();
+ if (image) {
+
+ for (y = 0; y < frameHeight; y++) {
+ /* RGB_ALPHA */
+ uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
+ uint8_t* dst_address = rows_pointer[(height - 1) - y0 - (frameHeight - 1) + y] + (x0 * 4);
+ int x;
+ unsigned int c;
+
+ for (x=0; x<frameWidth; x++) {
+ c = *(src_address++);
+ *(dst_address++) = rgba_getr(c);
+ *(dst_address++) = rgba_getg(c);
+ *(dst_address++) = rgba_getb(c);
+ *(dst_address++) = rgba_geta(c);
+ } // x
+ } // y
+
+ } // image
+ } // cel
+
+ fop->setProgress(0.1 + 0.8 * (double)(index+1) / (double)imageCount);
+
+ } // layer
+ } // frame
+
+ fprintf(xml_fp,
+ "\t</Frames>\n"
+ "</PixlyAnimation>\n"
+ );
+
+ /* If you are only writing one row at a time, this works */
+ for (y = 0; y < height; y++) {
+ /* write the line */
+ png_write_rows(png_ptr, rows_pointer+y, 1);
+
+ fop->setProgress(0.9 + 0.1 * (double)(y+1) / (double)height);
+ }
+
+ for (y = 0; y < height; y++) {
+ png_free(png_ptr, rows_pointer[y]);
+ }
+ png_free(png_ptr, rows_pointer);
+
+ /* It is REQUIRED to call this to finish writing the rest of the file */
+ png_write_end(png_ptr, info_ptr);
+
+ /* clean up after the write, and free any memory allocated */
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ /* all right */
+ return true;
+}
+#endif
+
+} // namespace app
diff --git a/src/base/path.cpp b/src/base/path.cpp
index 9744fc4..319e96b 100644
--- a/src/base/path.cpp
+++ b/src/base/path.cpp
@@ -85,6 +85,30 @@ std::string get_file_extension(const std::string& filename)
return result;
}
+std::string replace_extension(const std::string& filename, const std::string& extension)
+{
+ std::string::const_reverse_iterator rit;
+ std::string result;
+
+ // search for the first dot from the end of the string
+ for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
+ if (is_path_separator(*rit))
+ return result;
+ else if (*rit == '.')
+ break;
+ }
+
+ if (rit != filename.rend()) {
+ std::copy(filename.begin(), std::string::const_iterator(rit.base()),
+ std::back_inserter(result));
+ std::copy(extension.begin(), extension.end(),
+ std::back_inserter(result));
+ }
+
+ return result;
+}
+
+
std::string get_file_title(const std::string& filename)
{
std::string::const_reverse_iterator rit;
diff --git a/src/base/path.h b/src/base/path.h
index e58baa5..c394604 100644
--- a/src/base/path.h
+++ b/src/base/path.h
@@ -28,6 +28,9 @@ namespace base {
// Returns the extension of the file name (without the dot).
std::string get_file_extension(const std::string& filename);
+ // Returns the whole path with another extension.
+ std::string replace_extension(const std::string& filename, const std::string& extension);
+
// Returns the file name without path and without extension.
std::string get_file_title(const std::string& filename);
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git
More information about the Pkg-games-commits
mailing list