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.cap...@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 _______________________________________________ Pkg-games-commits mailing list Pkg-games-commits@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-games-commits