From d45e6a2ee1c38c29e933f62b45a9cd978ddf82f2 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Thu, 23 Jul 2015 13:28:39 -0700 Subject: [PATCH 01/10] Add metadata to text chunk in PNGs using setMetadata(). --- lib/Image.js | 4 ++- lib/ImagePrototypeInit.js | 34 +++++++++++++++++++++++ lib/obtain.js | 7 +++-- src/encoder/encoder.h | 2 ++ src/encoder/init.cpp | 12 +++++++- src/encoder/png_worker.cpp | 56 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 109 insertions(+), 6 deletions(-) diff --git a/lib/Image.js b/lib/Image.js index 74083061..9036b533 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -2,10 +2,12 @@ var lwip_image = require('../build/Release/lwip_image'); - function Image(pixelsBuf, width, height, trans) { + function Image(pixelsBuf, width, height, trans, metadata) { this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); this.__locked = false; this.__trans = trans; + // this.__metadata = metadata || null; + this.__metadata = metadata; } // EXPORTS diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js index 73649249..2cb01c37 100644 --- a/lib/ImagePrototypeInit.js +++ b/lib/ImagePrototypeInit.js @@ -47,6 +47,32 @@ this.__locked = false; }; + Image.prototype.getMetadata = function() { + if (!this.__metadata || this.__metadata === "") { + return null; + } else { + return this.__metadata; + } + }; + + Image.prototype.setMetadata = function(data) { + // this.__lock(); + // var that = this; + + // judges.setMetadata( + // arguments, + // function(data) { + // this.__metadata = data; + // }, + // function(err) { + // that.__release(); + // throw err; + // } + // ); + this.__metadata = data; + return data; + }; + Image.prototype.width = function() { return this.__lwip.width(); }; @@ -550,6 +576,13 @@ encoderCb ); } else if (type === 'png') { + console.log("*** ImagePrototypeInit.js (toBuffer) metadata: \n" + that.__metadata); + + var metadata = that.__metadata; + if (!metadata) { + metadata = ""; + } + util.normalizePngParams(params); return encoder.png( that.__lwip.buffer(), @@ -558,6 +591,7 @@ params.compression, params.interlaced, params.transparency === 'auto' ? that.__trans : params.transparency, + metadata, encoderCb ); } else if (type === 'gif') { diff --git a/lib/obtain.js b/lib/obtain.js index 23999c9c..42428ad3 100644 --- a/lib/obtain.js +++ b/lib/obtain.js @@ -28,8 +28,11 @@ opener = getOpener(type); fs.readFile(source, function(err, imbuff) { if (err) return callback(err); - opener(imbuff, function(err, pixelsBuf, width, height, channels, trans) { - callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); + + // var metadata = "meep"; + + opener(imbuff, function(err, pixelsBuf, width, height, channels, trans, metadata) { + callback(err, err ? null : new Image(pixelsBuf, width, height, trans, metadata)); }); }); } else if (source instanceof Buffer) { diff --git a/src/encoder/encoder.h b/src/encoder/encoder.h index a68468dc..38e709e3 100644 --- a/src/encoder/encoder.h +++ b/src/encoder/encoder.h @@ -53,6 +53,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { int compression, bool interlaced, bool trans, + char *metadata, NanCallback * callback ); ~EncodeToPngBufferWorker(); @@ -65,6 +66,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { int _compression; bool _interlaced; bool _trans; + char *_metadata; char * _pngbuf; size_t _pngbufsize; }; diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index 5aad47e9..270d07f3 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -31,7 +31,16 @@ NAN_METHOD(encodeToPngBuffer) { int compression = args[3].As()->Value(); bool interlaced = args[4]->BooleanValue(); bool trans = args[5]->BooleanValue(); - NanCallback * callback = new NanCallback(args[6].As()); + + int metadata_len = args[6].As()->Utf8Length(); + // char metadata = + // char *metadata = args[6].As().Utf8Value(); + char *metadata = (char *)malloc(metadata_len * sizeof(char)); + // char metadata[metadata_len]; + args[6].As()->WriteUtf8(metadata); + + NanCallback * callback = new NanCallback(args[7].As()); + // NanCallback * callback = new NanCallback(args[6].As()); NanAsyncQueueWorker( new EncodeToPngBufferWorker( @@ -41,6 +50,7 @@ NAN_METHOD(encodeToPngBuffer) { compression, interlaced, trans, + metadata, callback ) ); diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 1cfe527e..94f748cb 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -1,4 +1,5 @@ #include "encoder.h" +#include #define RGB_N_CHANNELS 3 #define RGBA_N_CHANNELS 4 @@ -10,17 +11,25 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker( int compression, bool interlaced, bool trans, + char *metadata, + // char *metadata, NanCallback * callback ): NanAsyncWorker(callback), _width(width), _height(height), - _compression(compression), _interlaced(interlaced), _trans(trans), + _compression(compression), _interlaced(interlaced), _trans(trans), _metadata(metadata), _pngbuf(NULL), _pngbufsize(0) { SaveToPersistent("buff", buff); // make sure buff isn't GC'ed _pixbuf = (unsigned char *) Buffer::Data(buff); + + // SaveToPersistent("metadata", metadata); + // _metadata = (char *)metadata; } EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} void EncodeToPngBufferWorker::Execute () { + + // cout << "** EncodeToPngBufferWorker _metadata: " << _metadata << "\n"; + int n_chan = _trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS; unsigned int rowBytes = _width * n_chan; int interlaceType; @@ -97,6 +106,49 @@ void EncodeToPngBufferWorker::Execute () { PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); + + // set metadata in tEXt chunk + png_text metadata; + metadata.compression = PNG_TEXT_COMPRESSION_NONE; + metadata.key = "lwip_data"; + metadata.text = _metadata; + + png_set_text(png_ptr, info_ptr, &metadata, 1); + + // test setting multiple text chunks + // png_text metadata[2]; + // metadata[0].compression = PNG_TEXT_COMPRESSION_NONE; + // metadata[0].key = "lwip_data"; + // metadata[0].text = _metadata; + + // metadata[1].compression = PNG_TEXT_COMPRESSION_NONE; + // metadata[1].key = "lwip_data2"; + // metadata[1].text = _metadata; + + // png_set_text(png_ptr, info_ptr, metadata, 2); + + + // === + // png_text *text_ptr = new png_text[1]; + + // text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; + // text_ptr[0].key = "data"; + // text_ptr[0].text = "meep"; + // text_ptr[0].text_length = 4; + // text_ptr[0].itxt_length = 0; + // // text_ptr[0].lang = NULL; + // // text_ptr[0].lang_key = NULL; + + // cout << text_ptr[0].text; + // cout << "*** 0\n"; + + // // png_set_text(png_ptr, info_ptr, text_ptr, 1); + // cout << "*** 1\n"; + // === + + + + png_set_compression_level(png_ptr, compLevel); pngWriteCbData buffinf = {NULL, 0}; @@ -164,4 +216,4 @@ void pngWriteCB(png_structp png_ptr, png_bytep data, png_size_t length) { memcpy(buffinf->buff + buffinf->buffsize, data, length); buffinf->buffsize += length; -} +} \ No newline at end of file From 9ad754fc6b0933fac55cf338f9d6716f93da0526 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Thu, 23 Jul 2015 13:29:14 -0700 Subject: [PATCH 02/10] Read metadata from PNGs text chunks using getMetadata(). --- src/decoder/buffer_worker.cpp | 21 ++++++++++++++---- src/decoder/decoder.h | 9 ++++---- src/decoder/gif_decoder.cpp | 2 +- src/decoder/jpeg_decoder.cpp | 2 +- src/decoder/png_decoder.cpp | 28 ++++++++++++++++++++++- src/lib/gif/egif_lib.c | 42 +++++++++++++++++------------------ src/lib/gif/gifalloc.c | 30 ++++++++++++------------- src/lib/jpeg/jcapimin.c | 2 +- 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index 534862e9..c0a9ffcc 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -1,11 +1,12 @@ #include "decoder.h" +#include DecodeBufferWorker::DecodeBufferWorker( NanCallback * callback, Local & buff, buf_dec_f_t decoder ): NanAsyncWorker(callback), _decoder(decoder), _pixbuf(NULL), _width(0), - _height(0), _channels(0), _trans(false) { + _height(0), _channels(0), _trans(false), _metadata("") { SaveToPersistent("buff", buff); // make sure buff isn't GC'ed _buffer = Buffer::Data(buff); _buffsize = Buffer::Length(buff); @@ -16,7 +17,13 @@ DecodeBufferWorker::~DecodeBufferWorker() {} void DecodeBufferWorker::Execute () { CImg * img = NULL; string err; - err = _decoder(_buffer, _buffsize, &img); + char * metadata; + + // cout << "** DecodeBufferWorker::Execute: " << metadata; + + err = _decoder(_buffer, _buffsize, &img, &metadata); + + if (img == NULL) { SetErrorMessage(err.c_str()); return; @@ -33,6 +40,10 @@ void DecodeBufferWorker::Execute () { _width = img->width(); _height = img->height(); _channels = 4; + _metadata = metadata; + + // cout << "*** DecodeBufferWorker::Execute: metadata ***\n" << _metadata; + delete img; return; } @@ -48,7 +59,9 @@ void DecodeBufferWorker::HandleOKCallback () { NanNew(_width), NanNew(_height), NanNew(_channels), - NanNew(_trans) + NanNew(_trans), + NanNew(_metadata) }; - callback->Call(6, argv); + // callback->Call(6, argv); + callback->Call(7, argv); } diff --git a/src/decoder/decoder.h b/src/decoder/decoder.h index 3467765b..aa7e4f74 100644 --- a/src/decoder/decoder.h +++ b/src/decoder/decoder.h @@ -25,7 +25,7 @@ using namespace v8; using namespace node; using namespace std; -typedef string (* buf_dec_f_t)(char *, size_t, CImg **); +typedef string (* buf_dec_f_t)(char *, size_t, CImg **, char **); class DecodeBufferWorker : public NanAsyncWorker { public: @@ -46,6 +46,7 @@ class DecodeBufferWorker : public NanAsyncWorker { size_t _height; int _channels; bool _trans; // transparency + char * _metadata; }; typedef struct { @@ -77,9 +78,9 @@ inline void lwip_jpeg_error_exit (j_common_ptr cinfo) { */ string toRGBA(CImg ** img); -string decode_jpeg_buffer(char * buffer, size_t size, CImg ** img); -string decode_png_buffer(char * buffer, size_t size, CImg ** img); -string decode_gif_buffer(char * buffer, size_t size, CImg ** img); +string decode_jpeg_buffer(char * buffer, size_t size, CImg ** img, char ** metadata); +string decode_png_buffer(char * buffer, size_t size, CImg ** img, char ** metadata); +string decode_gif_buffer(char * buffer, size_t size, CImg ** img, char ** metadata); void pngReadCB(png_structp png_ptr, png_bytep data, png_size_t length); int gifReadCB(GifFileType * gif, GifByteType * buf, int length); diff --git a/src/decoder/gif_decoder.cpp b/src/decoder/gif_decoder.cpp index e6a14ff8..9843ecae 100644 --- a/src/decoder/gif_decoder.cpp +++ b/src/decoder/gif_decoder.cpp @@ -4,7 +4,7 @@ #define ALPHA_OPAQUE 255 #define C_TRANS 0 -string decode_gif_buffer(char * buffer, size_t size, CImg ** cimg) { +string decode_gif_buffer(char * buffer, size_t size, CImg ** cimg, char ** metadata) { gifReadCbData buffinf = {(unsigned char *) buffer, size, 0}; GifFileType * gif = NULL; diff --git a/src/decoder/jpeg_decoder.cpp b/src/decoder/jpeg_decoder.cpp index df96bd16..4210a3a7 100644 --- a/src/decoder/jpeg_decoder.cpp +++ b/src/decoder/jpeg_decoder.cpp @@ -1,6 +1,6 @@ #include "decoder.h" -string decode_jpeg_buffer(char * buffer, size_t size, CImg ** cimg) { +string decode_jpeg_buffer(char * buffer, size_t size, CImg ** cimg, char ** metadata) { struct jpeg_decompress_struct cinfo; struct lwip_jpeg_error_mgr jerr; diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index b68873f7..acd948b7 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -1,6 +1,10 @@ #include "decoder.h" +#include + +string decode_png_buffer(char * buffer, size_t size, CImg ** cimg, char ** metadata) { + // cout << "*** decode_png_buffer() ***\n"; + // cout << "** decode_png_buffer: " << metadata; -string decode_png_buffer(char * buffer, size_t size, CImg ** cimg) { // check it's a valid png buffer if (size < 8 || png_sig_cmp((png_const_bytep) buffer, 0, 8)) { return "Invalid PNG buffer"; @@ -43,6 +47,28 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); + + // get metadata in first text chunk found with keyboard 'lwip_data' + png_textp text_ptr; + int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL); + bool metadata_found = false; + + for (int i = 0; i < num_comments; i++) { + if (strcmp(text_ptr[i].key, "lwip_data") == 0) { + int metadata_len = (strlen(text_ptr[i].text) + 1) * sizeof(char); + *metadata = (char *)malloc(metadata_len); + memcpy(*metadata, text_ptr[i].text, metadata_len); + metadata_found = true; + break; //TODO: handle multiple lwip_data text chunks? + } + } + + // if no text chunks with keyword 'lwip_data' are found, set metadata to an empty string + if (!metadata_found) { + *metadata = (char *)malloc(sizeof(char)); + *metadata[0] = '\0'; + } + if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; diff --git a/src/lib/gif/egif_lib.c b/src/lib/gif/egif_lib.c index 39a62b2e..c62e86c1 100644 --- a/src/lib/gif/egif_lib.c +++ b/src/lib/gif/egif_lib.c @@ -58,10 +58,10 @@ EGifOpenFileName(const char *FileName, const bool TestExistence, int *Error) GifFileType *GifFile; if (TestExistence) - FileHandle = open(FileName, O_WRONLY | O_CREAT | O_EXCL, + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_EXCL, S_IREAD | S_IWRITE); else - FileHandle = open(FileName, O_WRONLY | O_CREAT | O_TRUNC, + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); if (FileHandle == -1) { @@ -190,7 +190,7 @@ EGifGetGifVersion(GifFileType *GifFile) GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; int i, j; - /* + /* * Bulletproofing - always write GIF89 if we need to. * Note, we don't clear the gif89 flag here because * users of the sequential API might have called EGifSetGifVersion() @@ -217,7 +217,7 @@ EGifGetGifVersion(GifFileType *GifFile) || function == APPLICATION_EXT_FUNC_CODE) Private->gif89 = true; } - + if (Private->gif89) return GIF89_STAMP; else @@ -226,7 +226,7 @@ EGifGetGifVersion(GifFileType *GifFile) /****************************************************************************** Set the GIF version. In the extremely unlikely event that there is ever - another version, replace the bool argument with an enum in which the + another version, replace the bool argument with an enum in which the GIF87 value is 0 (numerically the same as bool false) and the GIF89 value is 1 (numerically the same as bool true). That way we'll even preserve object-file compatibility! @@ -241,7 +241,7 @@ void EGifSetGifVersion(GifFileType *GifFile, const bool gif89) /****************************************************************************** All writes to the GIF should go through this. ******************************************************************************/ -static int InternalWrite(GifFileType *GifFileOut, +static int InternalWrite(GifFileType *GifFileOut, const unsigned char *buf, size_t len) { GifFilePrivateType *Private = (GifFilePrivateType*)GifFileOut->Private; @@ -561,7 +561,7 @@ EGifPutExtensionLeader(GifFileType *GifFile, const int ExtCode) Put extension block data (see GIF manual) into a GIF file. ******************************************************************************/ int -EGifPutExtensionBlock(GifFileType *GifFile, +EGifPutExtensionBlock(GifFileType *GifFile, const int ExtLen, const void *Extension) { @@ -660,7 +660,7 @@ size_t EGifGCBToExtension(const GraphicsControlBlock *GCB, Replace the Graphics Control Block for a saved image, if it exists. ******************************************************************************/ -int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, +int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, GifFileType *GifFile, int ImageIndex) { int i; @@ -708,7 +708,7 @@ EGifPutCode(GifFileType *GifFile, int CodeSize, const GifByteType *CodeBlock) } /* No need to dump code size as Compression set up does any for us: */ - /* + /* * Buf = CodeSize; * if (InternalWrite(GifFile, &Buf, 1) != 1) { * GifFile->Error = E_GIF_ERR_WRITE_FAILED; @@ -891,7 +891,7 @@ EGifCompressLine(GifFileType *GifFile, while (i < LineLen) { /* Decode LineLen items. */ Pixel = Line[i++]; /* Get next pixel from stream. */ - /* Form a new unique key to search hash table for the code combines + /* Form a new unique key to search hash table for the code combines * CrntCode as Prefix string with Pixel as postfix char. */ NewKey = (((uint32_t) CrntCode) << 8) + Pixel; @@ -1047,9 +1047,9 @@ EGifBufferedOutput(GifFileType *GifFile, ******************************************************************************/ static int -EGifWriteExtensions(GifFileType *GifFileOut, - ExtensionBlock *ExtensionBlocks, - int ExtensionBlockCount) +EGifWriteExtensions(GifFileType *GifFileOut, + ExtensionBlock *ExtensionBlocks, + int ExtensionBlockCount) { if (ExtensionBlocks) { ExtensionBlock *ep; @@ -1072,9 +1072,9 @@ EGifWriteExtensions(GifFileType *GifFileOut, } int -EGifSpew(GifFileType *GifFileOut) +EGifSpew(GifFileType *GifFileOut) { - int i, j; + int i, j; if (EGifPutScreenDesc(GifFileOut, GifFileOut->SWidth, @@ -1094,7 +1094,7 @@ EGifSpew(GifFileType *GifFileOut) if (sp->RasterBits == NULL) continue; - if (EGifWriteExtensions(GifFileOut, + if (EGifWriteExtensions(GifFileOut, sp->ExtensionBlocks, sp->ExtensionBlockCount) == GIF_ERROR) return (GIF_ERROR); @@ -1109,8 +1109,8 @@ EGifSpew(GifFileType *GifFileOut) return (GIF_ERROR); if (sp->ImageDesc.Interlace) { - /* - * The way an interlaced image should be written - + /* + * The way an interlaced image should be written - * offsets and jumps... */ int InterlacedOffset[] = { 0, 4, 2, 1 }; @@ -1118,11 +1118,11 @@ EGifSpew(GifFileType *GifFileOut) int k; /* Need to perform 4 passes on the images: */ for (k = 0; k < 4; k++) - for (j = InterlacedOffset[k]; + for (j = InterlacedOffset[k]; j < SavedHeight; j += InterlacedJumps[k]) { - if (EGifPutLine(GifFileOut, - sp->RasterBits + j * SavedWidth, + if (EGifPutLine(GifFileOut, + sp->RasterBits + j * SavedWidth, SavedWidth) == GIF_ERROR) return (GIF_ERROR); } diff --git a/src/lib/gif/gifalloc.c b/src/lib/gif/gifalloc.c index 9fe3edf8..9862464e 100644 --- a/src/lib/gif/gifalloc.c +++ b/src/lib/gif/gifalloc.c @@ -13,7 +13,7 @@ #define MAX(x, y) (((x) > (y)) ? (x) : (y)) /****************************************************************************** - Miscellaneous utility functions + Miscellaneous utility functions ******************************************************************************/ /* return smallest bitfield size n will fit in */ @@ -29,7 +29,7 @@ GifBitSize(int n) } /****************************************************************************** - Color map object functions + Color map object functions ******************************************************************************/ /* @@ -46,7 +46,7 @@ GifMakeMapObject(int ColorCount, const GifColorType *ColorMap) if (ColorCount != (1 << GifBitSize(ColorCount))) { return ((ColorMapObject *) NULL); } - + Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); if (Object == (ColorMapObject *) NULL) { return ((ColorMapObject *) NULL); @@ -104,7 +104,7 @@ DumpColorMap(ColorMapObject *Object, #endif /* DEBUG */ /******************************************************************************* - Compute the union of two given color maps and return it. If result can't + Compute the union of two given color maps and return it. If result can't fit into 256 colors, NULL is returned, the allocated union otherwise. ColorIn1 is copied as is to ColorUnion, while colors from ColorIn2 are copied iff they didn't exist before. ColorTransIn2 maps the old @@ -131,14 +131,14 @@ GifUnionColorMap(const ColorMapObject *ColorIn1, if (ColorUnion == NULL) return (NULL); - /* + /* * Copy ColorIn1 to ColorUnion. */ for (i = 0; i < ColorIn1->ColorCount; i++) ColorUnion->Colors[i] = ColorIn1->Colors[i]; CrntSlot = ColorIn1->ColorCount; - /* + /* * Potentially obnoxious hack: * * Back CrntSlot down past all contiguous {0, 0, 0} slots at the end @@ -154,7 +154,7 @@ GifUnionColorMap(const ColorMapObject *ColorIn1, for (i = 0; i < ColorIn2->ColorCount && CrntSlot <= 256; i++) { /* Let's see if this color already exists: */ for (j = 0; j < ColorIn1->ColorCount; j++) - if (memcmp (&ColorIn1->Colors[j], &ColorIn2->Colors[i], + if (memcmp (&ColorIn1->Colors[j], &ColorIn2->Colors[i], sizeof(GifColorType)) == 0) break; @@ -178,7 +178,7 @@ GifUnionColorMap(const ColorMapObject *ColorIn1, if (RoundUpTo != ColorUnion->ColorCount) { register GifColorType *Map = ColorUnion->Colors; - /* + /* * Zero out slots up to next power of 2. * We know these slots exist because of the way ColorUnion's * start dimension was computed. @@ -212,7 +212,7 @@ GifApplyTranslation(SavedImage *Image, GifPixelType Translation[]) } /****************************************************************************** - Extension record functions + Extension record functions ******************************************************************************/ int GifAddExtensionBlock(int *ExtensionBlockCount, @@ -258,7 +258,7 @@ GifFreeExtensions(int *ExtensionBlockCount, return; for (ep = *ExtensionBlocks; - ep < (*ExtensionBlocks + *ExtensionBlockCount); + ep < (*ExtensionBlocks + *ExtensionBlockCount); ep++) (void)free((char *)ep->Bytes); (void)free((char *)*ExtensionBlocks); @@ -267,7 +267,7 @@ GifFreeExtensions(int *ExtensionBlockCount, } /****************************************************************************** - Image block allocation functions + Image block allocation functions ******************************************************************************/ /* Private Function: @@ -277,7 +277,7 @@ void FreeLastSavedImage(GifFileType *GifFile) { SavedImage *sp; - + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) return; @@ -307,7 +307,7 @@ FreeLastSavedImage(GifFileType *GifFile) } /* - * Append an image block to the SavedImages array + * Append an image block to the SavedImages array */ SavedImage * GifMakeSavedImage(GifFileType *GifFile, const SavedImage *CopyFrom) @@ -327,7 +327,7 @@ GifMakeSavedImage(GifFileType *GifFile, const SavedImage *CopyFrom) if (CopyFrom != NULL) { memcpy((char *)sp, CopyFrom, sizeof(SavedImage)); - /* + /* * Make our own allocated copies of the heap fields in the * copied record. This guards against potential aliasing * problems. @@ -391,7 +391,7 @@ GifFreeSavedImages(GifFileType *GifFile) if (sp->RasterBits != NULL) free((char *)sp->RasterBits); - + GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks); } free((char *)GifFile->SavedImages); diff --git a/src/lib/jpeg/jcapimin.c b/src/lib/jpeg/jcapimin.c index 639ce86f..a8cdc4d2 100644 --- a/src/lib/jpeg/jcapimin.c +++ b/src/lib/jpeg/jcapimin.c @@ -37,7 +37,7 @@ jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize) if (version != JPEG_LIB_VERSION) ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); if (structsize != SIZEOF(struct jpeg_compress_struct)) - ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, (int) SIZEOF(struct jpeg_compress_struct), (int) structsize); /* For debugging purposes, we zero the whole master structure. From bf4670f05a3510ff6e8e76d50f788d9bcdd58b52 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Thu, 23 Jul 2015 15:40:24 -0700 Subject: [PATCH 03/10] Fix bugs and removing comments. --- lib/Image.js | 1 - lib/ImagePrototypeInit.js | 15 -- lib/obtain.js | 7 +- src/decoder/buffer_worker.cpp | 9 +- src/decoder/gif_decoder.cpp | 4 + src/decoder/jpeg_decoder.cpp | 4 + src/decoder/png_decoder.cpp | 2 - src/encoder/init.cpp | 4 - src/encoder/png_worker.cpp | 48 +--- tests/.DS_Store | Bin 0 -> 6148 bytes tests/01.getters/index.js | 17 ++ tests/02.operations/121.setMetadata.js | 50 ++++ tests/05.stress/index.js | 308 ++++++++++++------------- tests/images/hasMetadata.png | Bin 0 -> 303 bytes tests/images/noMetadata.png | Bin 0 -> 255 bytes tests/imgs.js | 4 +- 16 files changed, 243 insertions(+), 230 deletions(-) create mode 100644 tests/.DS_Store create mode 100644 tests/02.operations/121.setMetadata.js create mode 100644 tests/images/hasMetadata.png create mode 100644 tests/images/noMetadata.png diff --git a/lib/Image.js b/lib/Image.js index 9036b533..947bf42d 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -6,7 +6,6 @@ this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); this.__locked = false; this.__trans = trans; - // this.__metadata = metadata || null; this.__metadata = metadata; } diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js index 2cb01c37..59627654 100644 --- a/lib/ImagePrototypeInit.js +++ b/lib/ImagePrototypeInit.js @@ -56,19 +56,6 @@ }; Image.prototype.setMetadata = function(data) { - // this.__lock(); - // var that = this; - - // judges.setMetadata( - // arguments, - // function(data) { - // this.__metadata = data; - // }, - // function(err) { - // that.__release(); - // throw err; - // } - // ); this.__metadata = data; return data; }; @@ -576,8 +563,6 @@ encoderCb ); } else if (type === 'png') { - console.log("*** ImagePrototypeInit.js (toBuffer) metadata: \n" + that.__metadata); - var metadata = that.__metadata; if (!metadata) { metadata = ""; diff --git a/lib/obtain.js b/lib/obtain.js index 42428ad3..0ade5895 100644 --- a/lib/obtain.js +++ b/lib/obtain.js @@ -28,9 +28,6 @@ opener = getOpener(type); fs.readFile(source, function(err, imbuff) { if (err) return callback(err); - - // var metadata = "meep"; - opener(imbuff, function(err, pixelsBuf, width, height, channels, trans, metadata) { callback(err, err ? null : new Image(pixelsBuf, width, height, trans, metadata)); }); @@ -47,8 +44,8 @@ } else if (typeof type === 'string') { // it's an encoded image opener = getOpener(type); - opener(source, function(err, pixelsBuf, width, height, channels, trans) { - callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); + opener(source, function(err, pixelsBuf, width, height, channels, trans, metadata) { + callback(err, err ? null : new Image(pixelsBuf, width, height, trans, metadata)); }); } else throw Error('Invalid type'); } else throw Error("Invalid source"); diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index c0a9ffcc..e1be2875 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -17,13 +17,10 @@ DecodeBufferWorker::~DecodeBufferWorker() {} void DecodeBufferWorker::Execute () { CImg * img = NULL; string err; - char * metadata; - - // cout << "** DecodeBufferWorker::Execute: " << metadata; + char * metadata = NULL; err = _decoder(_buffer, _buffsize, &img, &metadata); - if (img == NULL) { SetErrorMessage(err.c_str()); return; @@ -42,8 +39,6 @@ void DecodeBufferWorker::Execute () { _channels = 4; _metadata = metadata; - // cout << "*** DecodeBufferWorker::Execute: metadata ***\n" << _metadata; - delete img; return; } @@ -62,6 +57,6 @@ void DecodeBufferWorker::HandleOKCallback () { NanNew(_trans), NanNew(_metadata) }; - // callback->Call(6, argv); + callback->Call(7, argv); } diff --git a/src/decoder/gif_decoder.cpp b/src/decoder/gif_decoder.cpp index 9843ecae..cbe0618f 100644 --- a/src/decoder/gif_decoder.cpp +++ b/src/decoder/gif_decoder.cpp @@ -70,6 +70,10 @@ string decode_gif_buffer(char * buffer, size_t size, CImg ** cimg return GifErrorString(errcode); } + // TODO: implement getting metadata from GIFs; this is a placeholder + *metadata = (char *)malloc(sizeof(char)); + *metadata[0] = '\0'; + return ""; } diff --git a/src/decoder/jpeg_decoder.cpp b/src/decoder/jpeg_decoder.cpp index 4210a3a7..bc9b7001 100644 --- a/src/decoder/jpeg_decoder.cpp +++ b/src/decoder/jpeg_decoder.cpp @@ -53,5 +53,9 @@ string decode_jpeg_buffer(char * buffer, size_t size, CImg ** cim jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); + // TODO: implement getting metadata from GIFs; this is a placeholder + *metadata = (char *)malloc(sizeof(char)); + *metadata[0] = '\0'; + return ""; } diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index acd948b7..9b692b47 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -2,8 +2,6 @@ #include string decode_png_buffer(char * buffer, size_t size, CImg ** cimg, char ** metadata) { - // cout << "*** decode_png_buffer() ***\n"; - // cout << "** decode_png_buffer: " << metadata; // check it's a valid png buffer if (size < 8 || png_sig_cmp((png_const_bytep) buffer, 0, 8)) { diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index 270d07f3..e486268c 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -33,14 +33,10 @@ NAN_METHOD(encodeToPngBuffer) { bool trans = args[5]->BooleanValue(); int metadata_len = args[6].As()->Utf8Length(); - // char metadata = - // char *metadata = args[6].As().Utf8Value(); char *metadata = (char *)malloc(metadata_len * sizeof(char)); - // char metadata[metadata_len]; args[6].As()->WriteUtf8(metadata); NanCallback * callback = new NanCallback(args[7].As()); - // NanCallback * callback = new NanCallback(args[6].As()); NanAsyncQueueWorker( new EncodeToPngBufferWorker( diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 94f748cb..67441c14 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -28,8 +28,6 @@ EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} void EncodeToPngBufferWorker::Execute () { - // cout << "** EncodeToPngBufferWorker _metadata: " << _metadata << "\n"; - int n_chan = _trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS; unsigned int rowBytes = _width * n_chan; int interlaceType; @@ -108,46 +106,14 @@ void EncodeToPngBufferWorker::Execute () { ); // set metadata in tEXt chunk - png_text metadata; - metadata.compression = PNG_TEXT_COMPRESSION_NONE; - metadata.key = "lwip_data"; - metadata.text = _metadata; - - png_set_text(png_ptr, info_ptr, &metadata, 1); - - // test setting multiple text chunks - // png_text metadata[2]; - // metadata[0].compression = PNG_TEXT_COMPRESSION_NONE; - // metadata[0].key = "lwip_data"; - // metadata[0].text = _metadata; - - // metadata[1].compression = PNG_TEXT_COMPRESSION_NONE; - // metadata[1].key = "lwip_data2"; - // metadata[1].text = _metadata; - - // png_set_text(png_ptr, info_ptr, metadata, 2); - - - // === - // png_text *text_ptr = new png_text[1]; - - // text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; - // text_ptr[0].key = "data"; - // text_ptr[0].text = "meep"; - // text_ptr[0].text_length = 4; - // text_ptr[0].itxt_length = 0; - // // text_ptr[0].lang = NULL; - // // text_ptr[0].lang_key = NULL; - - // cout << text_ptr[0].text; - // cout << "*** 0\n"; - - // // png_set_text(png_ptr, info_ptr, text_ptr, 1); - // cout << "*** 1\n"; - // === - - + if (strlen(_metadata) > 0) { + png_text metadata; + metadata.compression = PNG_TEXT_COMPRESSION_NONE; + metadata.key = "lwip_data"; + metadata.text = _metadata; + png_set_text(png_ptr, info_ptr, &metadata, 1); + } png_set_compression_level(png_ptr, compLevel); diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..51cf4899f88f09720a14b37382d8fabd0271397c GIT binary patch literal 6148 zcmeHKUrXaa5Z|p;Q+K}srs54SB z9z?Fjh*%a3vThKk!AvwcULymzb_-0hAIxW`YA=hVyq`7v-Z#~fVH6m<>F1rX{UuG~iR}kRG8(k)wY@aSeVIhPzfzA|G6~Z` zBkd<)cI@f#S~AQ#ZF{}hi_Rdm=_k>V%w!;AnV(w~EN%P4aJcC>&Q4X-cj}|67;bOZ zs^W9q8IA0F%li0fYxnSLcXV-ib$xSt_w(VGUJDMNt&&xPWB3im6fI|7FNxFS6#V0y zaV#M*KnxHAZ;k=8ky*=c?#bzai2-8Zxf#IyL4YE<1`Ca9>wpS>AF;oThypg=B@l%{ z*I=O$dO)~N1=OkBTrs##2fr|JuE9d1PG?-r4C9!Yxw)ZmH9Po)3TNEaNHsA)3`{dH zr<*pO{|EoS|4%1Tix?mV-W3D9yzlKd;FZkTI`eXP)(X%QP!xR?C0|1CE(WSU=IP=XQo;E4f*~W2cWA*^|6FeU4u`}8 m4UEieJQ4;8Lx6FhIAP8b2F8pBhgJgpz~JfX=d#Wzp$P!B>rb-) literal 0 HcmV?d00001 diff --git a/tests/images/noMetadata.png b/tests/images/noMetadata.png new file mode 100644 index 0000000000000000000000000000000000000000..76a899a2136301b5248f33d30ca11cce2fd2c83a GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^DImYvN4 r-{FvWpn;K@jYq;DVF)k|6erAC!oZmE;Lu8-9~eAc{an^LB{Ts5kElJ! literal 0 HcmV?d00001 diff --git a/tests/imgs.js b/tests/imgs.js index fa957508..21668a5d 100644 --- a/tests/imgs.js +++ b/tests/imgs.js @@ -14,7 +14,9 @@ module.exports = { rgb: join(__dirname, imbase, 'rgb.png'), noex: join(__dirname, imbase, 'rgbpng'), trans: join(__dirname, imbase, 'trans.png'), - inv: join(__dirname, imbase, 'invalid.png') + inv: join(__dirname, imbase, 'invalid.png'), + hasMetadata: join(__dirname, imbase, 'hasMetadata.png'), + noMetadata: join(__dirname, imbase, 'noMetadata.png') }, gif: { gs: join(__dirname, imbase, 'gs.gif'), From 560e4635a1b8be8996afa9e6b9ef205e484e6414 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Thu, 23 Jul 2015 16:19:04 -0700 Subject: [PATCH 04/10] Add documentation. --- README.md | 19 +++++++++++++++++++ tests/02.operations/121.setMetadata.js | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d51504ac..1c682e14 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ 0. [Opacify](#opacify) 0. [Paste](#paste) 0. [Set pixel](#set-pixel) + 0. [Set metadata](#set-metadata) 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) @@ -47,6 +48,7 @@ 0. [PNG](#png) 0. [GIF](#gif) 0. [Write to file](#write-to-file) + 0. [Get metadata](#get-metadata) 0. [Batch operations](#batch-operations) 0. [Copyrights](#copyrights) @@ -554,6 +556,15 @@ Set the color of a pixel. 0. Extra caution is required when using this method in batch mode, as the dimensions of the image may change by the time this operation is called. +#### Set metadata + +Set the metadata in an image. This is currently only supported for PNG files. +Sets a tEXt chunk with the key `lwip_data` and comment as the given string. + +`image.setMetadata(metadata)` + +0. `metadata {String}`: a string of arbitrary length. + ### Getters #### Width @@ -685,6 +696,14 @@ Write encoded binary image data directly to a file. 0. `params {Object}`: **Optional** Format-specific parameters. 0. `callback {Function(err)}` +#### Get Metadata + +Get the textual metadata from an image. This is currently only supported for +tEXt chunks in PNG images, and will get the first tEXt chunk found with the key +`lwip_data`. + +`image.getMetadata()` + ### Batch operations Each of the [image operations](#image-operations) above can be done as part of diff --git a/tests/02.operations/121.setMetadata.js b/tests/02.operations/121.setMetadata.js index e27e5d03..f1e8c0f0 100644 --- a/tests/02.operations/121.setMetadata.js +++ b/tests/02.operations/121.setMetadata.js @@ -25,7 +25,7 @@ describe('lwip.setMetadata', function() { if (err) return done(err); assert(imgWithMetadata.getMetadata() === metadata); done(); - }) + }); }); }); }); @@ -41,7 +41,7 @@ describe('lwip.setMetadata', function() { if (err) return done(err); assert(imgNoMetadata.getMetadata() === null); done(); - }) + }); }); }); }); From ee942915c7dfea2b96a32428ff69e19844295a21 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 10:28:14 -0700 Subject: [PATCH 05/10] Fix comments. --- src/encoder/png_worker.cpp | 3 - tests/05.stress/index.js | 308 ++++++++++++++++++------------------- 2 files changed, 154 insertions(+), 157 deletions(-) diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 67441c14..b8ee7169 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -19,9 +19,6 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker( _pngbuf(NULL), _pngbufsize(0) { SaveToPersistent("buff", buff); // make sure buff isn't GC'ed _pixbuf = (unsigned char *) Buffer::Data(buff); - - // SaveToPersistent("metadata", metadata); - // _metadata = (char *)metadata; } EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} diff --git a/tests/05.stress/index.js b/tests/05.stress/index.js index 99bab999..f4ba2395 100644 --- a/tests/05.stress/index.js +++ b/tests/05.stress/index.js @@ -1,168 +1,168 @@ -// var join = require('path').join, -// fs = require('fs'), -// assert = require('assert'), -// async = require('async'), -// mkdirp = require('mkdirp'), -// lwip = require('../../'), -// utils = require('../utils'), -// imgs = require('../imgs'); +var join = require('path').join, + fs = require('fs'), + assert = require('assert'), + async = require('async'), + mkdirp = require('mkdirp'), + lwip = require('../../'), + utils = require('../utils'), + imgs = require('../imgs'); -// var tmpDir = join(__dirname, '../results'), -// outpathJpeg = join(tmpDir, 'stress.jpg'), -// outpathPng = join(tmpDir, 'stress.png'), -// outpathGif = join(tmpDir, 'stress.gif'); +var tmpDir = join(__dirname, '../results'), + outpathJpeg = join(tmpDir, 'stress.jpg'), + outpathPng = join(tmpDir, 'stress.png'), + outpathGif = join(tmpDir, 'stress.gif'); -// describe('stress tests', function() { +describe('stress tests', function() { -// this.timeout(120000); // 120 seconds per test + this.timeout(120000); // 120 seconds per test -// before(function(done) { -// mkdirp(tmpDir, done); -// }); + before(function(done) { + mkdirp(tmpDir, done); + }); -// describe('open image 300 times (in parallel) and save to disk as jpeg', function() { -// it('should succeed', function(done) { -// async.times(300, function(i, done) { -// lwip.open(imgs.png.rgb, 'png', function(err, image) { -// if (err) return done(err); -// image.writeFile(outpathJpeg, 'jpeg', { -// quality: 50 -// }, done); -// }); -// }, done); -// }); -// }); + describe('open image 300 times (in parallel) and save to disk as jpeg', function() { + it('should succeed', function(done) { + async.times(300, function(i, done) { + lwip.open(imgs.png.rgb, 'png', function(err, image) { + if (err) return done(err); + image.writeFile(outpathJpeg, 'jpeg', { + quality: 50 + }, done); + }); + }, done); + }); + }); -// describe('open image 100 times (in parallel) and save to disk as png (high compression, interlaced)', function() { -// it('should succeed', function(done) { -// async.times(100, function(i, done) { -// lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { -// if (err) return done(err); -// image.writeFile(outpathPng, 'png', { -// compression: 'high', -// interlaced: true -// }, done); -// }); -// }, done); -// }); -// }); + describe('open image 100 times (in parallel) and save to disk as png (high compression, interlaced)', function() { + it('should succeed', function(done) { + async.times(100, function(i, done) { + lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { + if (err) return done(err); + image.writeFile(outpathPng, 'png', { + compression: 'high', + interlaced: true + }, done); + }); + }, done); + }); + }); -// describe('open image 300 times (in parallel) and save to disk as png (fast compression, not interlaced)', function() { -// it('should succeed', function(done) { -// async.times(300, function(i, done) { -// lwip.open(imgs.gif.rgb, 'gif', function(err, image) { -// if (err) return done(err); -// image.writeFile(outpathPng, 'png', { -// compression: 'fast', -// interlaced: false -// }, done); -// }); -// }, done); -// }); -// }); + describe('open image 300 times (in parallel) and save to disk as png (fast compression, not interlaced)', function() { + it('should succeed', function(done) { + async.times(300, function(i, done) { + lwip.open(imgs.gif.rgb, 'gif', function(err, image) { + if (err) return done(err); + image.writeFile(outpathPng, 'png', { + compression: 'fast', + interlaced: false + }, done); + }); + }, done); + }); + }); -// describe('open image 300 times (in parallel) and save to disk as gif (128 colors, not interlaced, transparent)', function() { -// it('should succeed', function(done) { -// async.times(300, function(i, done) { -// lwip.open(imgs.png.trans, 'png', function(err, image) { -// if (err) return done(err); -// image.writeFile(outpathGif, 'gif', { -// colors: 128, -// transparency: true, -// threshold: 60 -// }, done); -// }); -// }, done); -// }); -// }); + describe('open image 300 times (in parallel) and save to disk as gif (128 colors, not interlaced, transparent)', function() { + it('should succeed', function(done) { + async.times(300, function(i, done) { + lwip.open(imgs.png.trans, 'png', function(err, image) { + if (err) return done(err); + image.writeFile(outpathGif, 'gif', { + colors: 128, + transparency: true, + threshold: 60 + }, done); + }); + }, done); + }); + }); -// describe('7 random manipulations for 50 images (in parallel)', function() { -// it('should succeed', function(done) { -// async.times(50, function(i, done) { -// lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { -// if (err) return done(err); -// var batch = image.batch(); -// var ops = utils.generateRandomBatch(batch, 7); -// batch.writeFile(join(tmpDir, 'stress-rnd-' + i + '.jpg'), 'jpeg', { -// quality: 50 -// }, function(err) { -// if (err) return done(err); -// var data = ops.join('\n'); -// fs.writeFile(join(tmpDir, 'stress-rnd-' + i + '.txt'), data, done); -// }); -// }); -// }, done); -// }); -// }); + describe('7 random manipulations for 50 images (in parallel)', function() { + it('should succeed', function(done) { + async.times(50, function(i, done) { + lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { + if (err) return done(err); + var batch = image.batch(); + var ops = utils.generateRandomBatch(batch, 7); + batch.writeFile(join(tmpDir, 'stress-rnd-' + i + '.jpg'), 'jpeg', { + quality: 50 + }, function(err) { + if (err) return done(err); + var data = ops.join('\n'); + fs.writeFile(join(tmpDir, 'stress-rnd-' + i + '.txt'), data, done); + }); + }); + }, done); + }); + }); -// describe('rotate an image 30 times (up to 90degs) (1)', function() { -// it('should succeed', function(done) { -// var a = 3; -// lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { -// if (err) return done(err); -// async.timesSeries(90 / a, function(i, done) { -// image.rotate(a, utils.getRandomColor(), done); -// }, function(err) { -// if (err) return done(err); -// image.writeFile(outpathJpeg, 'jpeg', { -// quality: 90 -// }, done); -// }); -// }); -// }); -// }); + describe('rotate an image 30 times (up to 90degs) (1)', function() { + it('should succeed', function(done) { + var a = 3; + lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { + if (err) return done(err); + async.timesSeries(90 / a, function(i, done) { + image.rotate(a, utils.getRandomColor(), done); + }, function(err) { + if (err) return done(err); + image.writeFile(outpathJpeg, 'jpeg', { + quality: 90 + }, done); + }); + }); + }); + }); -// describe('rotate an image 30 times (up to 90degs) (2)', function() { -// it('should succeed', function(done) { -// var a = 3; -// lwip.open(imgs.png.trans, 'png', function(err, image) { -// if (err) return done(err); -// async.timesSeries(90 / a, function(i, done) { -// image.rotate(a, utils.getRandomColor(), done); -// }, function(err) { -// if (err) return done(err); -// image.writeFile(outpathPng, 'png', { -// compression: 'fast', -// interlaced: false -// }, done); -// }); -// }); -// }); -// }); + describe('rotate an image 30 times (up to 90degs) (2)', function() { + it('should succeed', function(done) { + var a = 3; + lwip.open(imgs.png.trans, 'png', function(err, image) { + if (err) return done(err); + async.timesSeries(90 / a, function(i, done) { + image.rotate(a, utils.getRandomColor(), done); + }, function(err) { + if (err) return done(err); + image.writeFile(outpathPng, 'png', { + compression: 'fast', + interlaced: false + }, done); + }); + }); + }); + }); -// describe('25 random manipulations on one image (1)', function() { -// it('should succeed', function(done) { -// lwip.open(imgs.png.rgb, 'png', function(err, image) { -// if (err) return done(err); -// var batch = image.batch(); -// var ops = utils.generateRandomBatch(batch, 25); -// batch.writeFile(join(tmpDir, 'stress-25rnd.jpg'), 'jpeg', { -// quality: 85 -// }, function(err) { -// if (err) return done(err); -// var data = ops.join('\n'); -// fs.writeFile(join(tmpDir, 'stress-25rnd.jpg.txt'), data, done); -// }); -// }); -// }); -// }); + describe('25 random manipulations on one image (1)', function() { + it('should succeed', function(done) { + lwip.open(imgs.png.rgb, 'png', function(err, image) { + if (err) return done(err); + var batch = image.batch(); + var ops = utils.generateRandomBatch(batch, 25); + batch.writeFile(join(tmpDir, 'stress-25rnd.jpg'), 'jpeg', { + quality: 85 + }, function(err) { + if (err) return done(err); + var data = ops.join('\n'); + fs.writeFile(join(tmpDir, 'stress-25rnd.jpg.txt'), data, done); + }); + }); + }); + }); -// describe('25 random manipulations on one image (2)', function() { -// it('should succeed', function(done) { -// lwip.open(imgs.png.trans, 'png', function(err, image) { -// if (err) return done(err); -// var batch = image.batch(); -// var ops = utils.generateRandomBatch(batch, 25); -// batch.writeFile(join(tmpDir, 'stress-25rnd.png'), 'png', { -// compression: 'fast', -// interlaced: false -// }, function(err) { -// if (err) return done(err); -// var data = ops.join('\n'); -// fs.writeFile(join(tmpDir, 'stress-25rnd.png.txt'), data, done); -// }); -// }); -// }); -// }); + describe('25 random manipulations on one image (2)', function() { + it('should succeed', function(done) { + lwip.open(imgs.png.trans, 'png', function(err, image) { + if (err) return done(err); + var batch = image.batch(); + var ops = utils.generateRandomBatch(batch, 25); + batch.writeFile(join(tmpDir, 'stress-25rnd.png'), 'png', { + compression: 'fast', + interlaced: false + }, function(err) { + if (err) return done(err); + var data = ops.join('\n'); + fs.writeFile(join(tmpDir, 'stress-25rnd.png.txt'), data, done); + }); + }); + }); + }); -// }); +}); From a7b5c521cb34a1fc7d9986c85765d29a5427329f Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 16:45:08 -0700 Subject: [PATCH 06/10] Can set metadata to empty string; can call setMetadata(null) to remove metadata. --- lib/ImagePrototypeInit.js | 17 ++++++----------- src/encoder/encoder.h | 4 ++-- src/encoder/init.cpp | 15 +++++++++++---- src/encoder/png_worker.cpp | 8 ++------ 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js index 59627654..d5c96d4c 100644 --- a/lib/ImagePrototypeInit.js +++ b/lib/ImagePrototypeInit.js @@ -48,14 +48,14 @@ }; Image.prototype.getMetadata = function() { - if (!this.__metadata || this.__metadata === "") { - return null; - } else { - return this.__metadata; - } + return this.__metadata; }; Image.prototype.setMetadata = function(data) { + if (typeof data != "string" && data != null) { + throw Error("Metadata must be a string or null"); + } + this.__metadata = data; return data; }; @@ -563,11 +563,6 @@ encoderCb ); } else if (type === 'png') { - var metadata = that.__metadata; - if (!metadata) { - metadata = ""; - } - util.normalizePngParams(params); return encoder.png( that.__lwip.buffer(), @@ -576,7 +571,7 @@ params.compression, params.interlaced, params.transparency === 'auto' ? that.__trans : params.transparency, - metadata, + that.__metadata, encoderCb ); } else if (type === 'gif') { diff --git a/src/encoder/encoder.h b/src/encoder/encoder.h index 38e709e3..e20f3a88 100644 --- a/src/encoder/encoder.h +++ b/src/encoder/encoder.h @@ -53,7 +53,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { int compression, bool interlaced, bool trans, - char *metadata, + char * metadata, NanCallback * callback ); ~EncodeToPngBufferWorker(); @@ -66,7 +66,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { int _compression; bool _interlaced; bool _trans; - char *_metadata; + char * _metadata; char * _pngbuf; size_t _pngbufsize; }; diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index e486268c..f09e5faf 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -1,4 +1,5 @@ #include "encoder.h" +#include // encoder.jpeg(pixbuf, width, height, quality, callback) NAN_METHOD(encodeToJpegBuffer) { @@ -21,7 +22,7 @@ NAN_METHOD(encodeToJpegBuffer) { NanReturnUndefined(); } -// encoder.png(pixbuf, width, height, compression, interlaced, trans, callback) +// encoder.png(pixbuf, width, height, compression, interlaced, trans, metadata, callback) NAN_METHOD(encodeToPngBuffer) { NanScope(); @@ -32,9 +33,15 @@ NAN_METHOD(encodeToPngBuffer) { bool interlaced = args[4]->BooleanValue(); bool trans = args[5]->BooleanValue(); - int metadata_len = args[6].As()->Utf8Length(); - char *metadata = (char *)malloc(metadata_len * sizeof(char)); - args[6].As()->WriteUtf8(metadata); + char * metadata; + + if (args[6]->IsNull() || args[6]->IsUndefined()) { + metadata = NULL; + } else { + int metadata_len = args[6].As()->Utf8Length(); + metadata = (char *)malloc((metadata_len + 1) * sizeof(char)); + args[6].As()->WriteUtf8(metadata); + } NanCallback * callback = new NanCallback(args[7].As()); diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index b8ee7169..75f12093 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -11,8 +11,7 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker( int compression, bool interlaced, bool trans, - char *metadata, - // char *metadata, + char * metadata, NanCallback * callback ): NanAsyncWorker(callback), _width(width), _height(height), _compression(compression), _interlaced(interlaced), _trans(trans), _metadata(metadata), @@ -24,7 +23,6 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker( EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} void EncodeToPngBufferWorker::Execute () { - int n_chan = _trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS; unsigned int rowBytes = _width * n_chan; int interlaceType; @@ -102,13 +100,11 @@ void EncodeToPngBufferWorker::Execute () { PNG_FILTER_TYPE_DEFAULT ); - // set metadata in tEXt chunk - if (strlen(_metadata) > 0) { + if (_metadata != NULL) { png_text metadata; metadata.compression = PNG_TEXT_COMPRESSION_NONE; metadata.key = "lwip_data"; metadata.text = _metadata; - png_set_text(png_ptr, info_ptr, &metadata, 1); } From dd1c594dedb026ec5ef0adc6713de1e5c8db9ad3 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 16:46:01 -0700 Subject: [PATCH 07/10] Return null if tEXt chunk with lwip_data key not found. --- src/decoder/buffer_worker.cpp | 11 ++++++++++- src/decoder/png_decoder.cpp | 7 ------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index e1be2875..e6f23537 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -45,6 +45,14 @@ void DecodeBufferWorker::Execute () { void DecodeBufferWorker::HandleOKCallback () { NanScope(); + + Local metadata; + if (_metadata == NULL) { + metadata = NanNull(); + } else { + metadata = NanNew(_metadata); + } + Local argv[] = { NanNull(), NanBufferUse( @@ -55,7 +63,8 @@ void DecodeBufferWorker::HandleOKCallback () { NanNew(_height), NanNew(_channels), NanNew(_trans), - NanNew(_metadata) + metadata + // _metadata ? NanNew(_metadata) : NanNull() }; callback->Call(7, argv); diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index 9b692b47..5a5fb85f 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -56,17 +56,10 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg int metadata_len = (strlen(text_ptr[i].text) + 1) * sizeof(char); *metadata = (char *)malloc(metadata_len); memcpy(*metadata, text_ptr[i].text, metadata_len); - metadata_found = true; break; //TODO: handle multiple lwip_data text chunks? } } - // if no text chunks with keyword 'lwip_data' are found, set metadata to an empty string - if (!metadata_found) { - *metadata = (char *)malloc(sizeof(char)); - *metadata[0] = '\0'; - } - if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; From bc7da98ea59a92a0a3ec367bb6431a79c1c89143 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 17:33:16 -0700 Subject: [PATCH 08/10] Add more tests. --- .gitignore | 1 + src/decoder/buffer_worker.cpp | 1 - src/decoder/png_decoder.cpp | 1 - src/encoder/init.cpp | 1 - src/encoder/png_worker.cpp | 1 - tests/02.operations/121.setMetadata.js | 52 +++++++++++++++++++++++++- 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 344c97ca..221a9ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules npm-debug.log tests/results .idea +tests/.DS_Store diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index e6f23537..acc31371 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -1,5 +1,4 @@ #include "decoder.h" -#include DecodeBufferWorker::DecodeBufferWorker( NanCallback * callback, diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index 5a5fb85f..310586a4 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -1,5 +1,4 @@ #include "decoder.h" -#include string decode_png_buffer(char * buffer, size_t size, CImg ** cimg, char ** metadata) { diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index f09e5faf..7e5b67df 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -1,5 +1,4 @@ #include "encoder.h" -#include // encoder.jpeg(pixbuf, width, height, quality, callback) NAN_METHOD(encodeToJpegBuffer) { diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 75f12093..5efc4cc5 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -1,5 +1,4 @@ #include "encoder.h" -#include #define RGB_N_CHANNELS 3 #define RGBA_N_CHANNELS 4 diff --git a/tests/02.operations/121.setMetadata.js b/tests/02.operations/121.setMetadata.js index f1e8c0f0..52acfe73 100644 --- a/tests/02.operations/121.setMetadata.js +++ b/tests/02.operations/121.setMetadata.js @@ -2,7 +2,8 @@ var join = require('path').join, assert = require('assert'), mkdirp = require('mkdirp'), lwip = require('../../'), - imgs = require('../imgs'); + imgs = require('../imgs'), + should = require('should'); var tmpDir = join(__dirname, '../results'); @@ -45,6 +46,55 @@ describe('lwip.setMetadata', function() { }); }); }); + + it('should throw error if non-string metadata is set', function(done) { + var filename = 'noMetadata.png'; + + lwip.create(1, 1, function(err, img) { + if (err) return done(err);; + img.setMetadata.bind(img, {}).should.throwError(); + img.setMetadata.bind(img, function(){}).should.throwError(); + img.setMetadata.bind(img, 42).should.throwError(); + done(); + }); + }); + + it('should remove metadata if called with null parameter', function(done) { + lwip.open(imgs.png.hasMetadata, function(err, img) { + var filename = 'noMetadata.png'; + + assert(img.getMetadata() === 'Lorem ipsum dolor sit amet'); + + img.setMetadata(null); + + img.writeFile(join(tmpDir, filename), function(err) { + lwip.open(join(tmpDir, filename), function(err, imgWithMetadata) { + if (err) return done(err); + assert(imgWithMetadata.getMetadata() === null); + done(); + }); + }); + }); + }); + + it('can reset metadata on image with existing metadata', function(done) { + lwip.open(imgs.png.hasMetadata, function(err, img) { + var filename = 'changedMetadata.png'; + var oldMetadata = 'Lorem ipsum dolor sit amet'; + var newMetadata = 'The quick brown fox jumps over the lazy dog'; + + assert(img.getMetadata() === oldMetadata); + img.setMetadata(newMetadata); + + img.writeFile(join(tmpDir, filename), function(err) { + lwip.open(join(tmpDir, filename), function(err, imgWithMetadata) { + if (err) return done(err); + assert(imgWithMetadata.getMetadata() === newMetadata); + done(); + }); + }); + }); + }); }); }); From 453355aa459a5ad09e5d7476400f5adc85b7e0e8 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 17:44:27 -0700 Subject: [PATCH 09/10] Update docs. --- README.md | 8 +++++--- src/decoder/buffer_worker.cpp | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c682e14..890a9429 100644 --- a/README.md +++ b/README.md @@ -559,11 +559,13 @@ Set the color of a pixel. #### Set metadata Set the metadata in an image. This is currently only supported for PNG files. -Sets a tEXt chunk with the key `lwip_data` and comment as the given string. +Sets a tEXt chunk with the key `lwip_data` and comment as the given string. If +called with a `null` parameter, removes existing metadata from the image, +if present. `image.setMetadata(metadata)` -0. `metadata {String}`: a string of arbitrary length. +0. `metadata {String}`: a string of arbitrary length, or null. ### Getters @@ -700,7 +702,7 @@ Write encoded binary image data directly to a file. Get the textual metadata from an image. This is currently only supported for tEXt chunks in PNG images, and will get the first tEXt chunk found with the key -`lwip_data`. +`lwip_data`. If none is found, returns null. `image.getMetadata()` diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index acc31371..f2256f36 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -63,7 +63,6 @@ void DecodeBufferWorker::HandleOKCallback () { NanNew(_channels), NanNew(_trans), metadata - // _metadata ? NanNew(_metadata) : NanNull() }; callback->Call(7, argv); From 112332a3cffb9333e13dc46e331af0e11c2ea80d Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Mon, 27 Jul 2015 18:04:28 -0700 Subject: [PATCH 10/10] Remove unnecessary variable. --- src/decoder/png_decoder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index 310586a4..9e97ced3 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -48,7 +48,6 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg // get metadata in first text chunk found with keyboard 'lwip_data' png_textp text_ptr; int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL); - bool metadata_found = false; for (int i = 0; i < num_comments; i++) { if (strcmp(text_ptr[i].key, "lwip_data") == 0) {