Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for reading and writing PNG metadata #165

Merged
merged 10 commits into from
Aug 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
npm-debug.log npm-debug.log
tests/results tests/results
.idea .idea
tests/.DS_Store
21 changes: 21 additions & 0 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
0. [Opacify](#opacify) 0. [Opacify](#opacify)
0. [Paste](#paste) 0. [Paste](#paste)
0. [Set pixel](#set-pixel) 0. [Set pixel](#set-pixel)
0. [Set metadata](#set-metadata)
0. [Getters](#getters) 0. [Getters](#getters)
0. [Width](#width) 0. [Width](#width)
0. [Height](#height) 0. [Height](#height)
Expand All @@ -47,6 +48,7 @@
0. [PNG](#png) 0. [PNG](#png)
0. [GIF](#gif) 0. [GIF](#gif)
0. [Write to file](#write-to-file) 0. [Write to file](#write-to-file)
0. [Get metadata](#get-metadata)
0. [Batch operations](#batch-operations) 0. [Batch operations](#batch-operations)
0. [Copyrights](#copyrights) 0. [Copyrights](#copyrights)


Expand Down Expand Up @@ -554,6 +556,17 @@ Set the color of a pixel.
0. Extra caution is required when using this method in batch mode, as the 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. 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. 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, or null.

### Getters ### Getters


#### Width #### Width
Expand Down Expand Up @@ -685,6 +698,14 @@ Write encoded binary image data directly to a file.
0. `params {Object}`: **Optional** Format-specific parameters. 0. `params {Object}`: **Optional** Format-specific parameters.
0. `callback {Function(err)}` 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`. If none is found, returns null.

`image.getMetadata()`

### Batch operations ### Batch operations


Each of the [image operations](#image-operations) above can be done as part of Each of the [image operations](#image-operations) above can be done as part of
Expand Down
3 changes: 2 additions & 1 deletion lib/Image.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@


var lwip_image = require('../build/Release/lwip_image'); 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.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height);
this.__locked = false; this.__locked = false;
this.__trans = trans; this.__trans = trans;
this.__metadata = metadata;
} }


// EXPORTS // EXPORTS
Expand Down
14 changes: 14 additions & 0 deletions lib/ImagePrototypeInit.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@
this.__locked = false; this.__locked = false;
}; };


Image.prototype.getMetadata = function() {
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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are different types of 'data' handled?

Eventually this value will go all the way to here, at which time it will be casted to a string.

If users pass here an object, for example, do they expect us to serialize it for them? What if they pass in a function?

In the docs we say that 'data' should be a string. So we should probably enforce it here by doing type checks and throwing an exception if needed, as done in other methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I think it would make sense to only allow metadata to be set to a string, so I added that check.

I've also added the option to pass in null to setMetadata, which will cause the image to be saved without any tEXt chunks when writeFile is called.

return data;
};

Image.prototype.width = function() { Image.prototype.width = function() {
return this.__lwip.width(); return this.__lwip.width();
}; };
Expand Down Expand Up @@ -558,6 +571,7 @@
params.compression, params.compression,
params.interlaced, params.interlaced,
params.transparency === 'auto' ? that.__trans : params.transparency, params.transparency === 'auto' ? that.__trans : params.transparency,
that.__metadata,
encoderCb encoderCb
); );
} else if (type === 'gif') { } else if (type === 'gif') {
Expand Down
8 changes: 4 additions & 4 deletions lib/obtain.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
opener = getOpener(type); opener = getOpener(type);
fs.readFile(source, function(err, imbuff) { fs.readFile(source, function(err, imbuff) {
if (err) return callback(err); if (err) return callback(err);
opener(imbuff, function(err, pixelsBuf, width, height, channels, trans) { opener(imbuff, function(err, pixelsBuf, width, height, channels, trans, metadata) {
callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); callback(err, err ? null : new Image(pixelsBuf, width, height, trans, metadata));
}); });
}); });
} else if (source instanceof Buffer) { } else if (source instanceof Buffer) {
Expand All @@ -44,8 +44,8 @@
} else if (typeof type === 'string') { } else if (typeof type === 'string') {
// it's an encoded image // it's an encoded image
opener = getOpener(type); opener = getOpener(type);
opener(source, function(err, pixelsBuf, width, height, channels, trans) { opener(source, function(err, pixelsBuf, width, height, channels, trans, metadata) {
callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); callback(err, err ? null : new Image(pixelsBuf, width, height, trans, metadata));
}); });
} else throw Error('Invalid type'); } else throw Error('Invalid type');
} else throw Error("Invalid source"); } else throw Error("Invalid source");
Expand Down
23 changes: 19 additions & 4 deletions src/decoder/buffer_worker.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DecodeBufferWorker::DecodeBufferWorker(
Local<Object> & buff, Local<Object> & buff,
buf_dec_f_t decoder buf_dec_f_t decoder
): NanAsyncWorker(callback), _decoder(decoder), _pixbuf(NULL), _width(0), ): 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 SaveToPersistent("buff", buff); // make sure buff isn't GC'ed
_buffer = Buffer::Data(buff); _buffer = Buffer::Data(buff);
_buffsize = Buffer::Length(buff); _buffsize = Buffer::Length(buff);
Expand All @@ -16,7 +16,10 @@ DecodeBufferWorker::~DecodeBufferWorker() {}
void DecodeBufferWorker::Execute () { void DecodeBufferWorker::Execute () {
CImg<unsigned char> * img = NULL; CImg<unsigned char> * img = NULL;
string err; string err;
err = _decoder(_buffer, _buffsize, &img); char * metadata = NULL;

err = _decoder(_buffer, _buffsize, &img, &metadata);

if (img == NULL) { if (img == NULL) {
SetErrorMessage(err.c_str()); SetErrorMessage(err.c_str());
return; return;
Expand All @@ -33,12 +36,22 @@ void DecodeBufferWorker::Execute () {
_width = img->width(); _width = img->width();
_height = img->height(); _height = img->height();
_channels = 4; _channels = 4;
_metadata = metadata;

delete img; delete img;
return; return;
} }


void DecodeBufferWorker::HandleOKCallback () { void DecodeBufferWorker::HandleOKCallback () {
NanScope(); NanScope();

Local<v8::Primitive> metadata;
if (_metadata == NULL) {
metadata = NanNull();
} else {
metadata = NanNew<String>(_metadata);
}

Local<Value> argv[] = { Local<Value> argv[] = {
NanNull(), NanNull(),
NanBufferUse( NanBufferUse(
Expand All @@ -48,7 +61,9 @@ void DecodeBufferWorker::HandleOKCallback () {
NanNew<Number>(_width), NanNew<Number>(_width),
NanNew<Number>(_height), NanNew<Number>(_height),
NanNew<Number>(_channels), NanNew<Number>(_channels),
NanNew<Boolean>(_trans) NanNew<Boolean>(_trans),
metadata
}; };
callback->Call(6, argv);
callback->Call(7, argv);
} }
9 changes: 5 additions & 4 deletions src/decoder/decoder.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ using namespace v8;
using namespace node; using namespace node;
using namespace std; using namespace std;


typedef string (* buf_dec_f_t)(char *, size_t, CImg<unsigned char> **); typedef string (* buf_dec_f_t)(char *, size_t, CImg<unsigned char> **, char **);


class DecodeBufferWorker : public NanAsyncWorker { class DecodeBufferWorker : public NanAsyncWorker {
public: public:
Expand All @@ -46,6 +46,7 @@ class DecodeBufferWorker : public NanAsyncWorker {
size_t _height; size_t _height;
int _channels; int _channels;
bool _trans; // transparency bool _trans; // transparency
char * _metadata;
}; };


typedef struct { typedef struct {
Expand Down Expand Up @@ -77,9 +78,9 @@ inline void lwip_jpeg_error_exit (j_common_ptr cinfo) {
*/ */
string toRGBA(CImg<unsigned char> ** img); string toRGBA(CImg<unsigned char> ** img);


string decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** img); string decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** img, char ** metadata);
string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** img); string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** img, char ** metadata);
string decode_gif_buffer(char * buffer, size_t size, CImg<unsigned char> ** img); string decode_gif_buffer(char * buffer, size_t size, CImg<unsigned char> ** img, char ** metadata);


void pngReadCB(png_structp png_ptr, png_bytep data, png_size_t length); void pngReadCB(png_structp png_ptr, png_bytep data, png_size_t length);
int gifReadCB(GifFileType * gif, GifByteType * buf, int length); int gifReadCB(GifFileType * gif, GifByteType * buf, int length);
Expand Down
6 changes: 5 additions & 1 deletion src/decoder/gif_decoder.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#define ALPHA_OPAQUE 255 #define ALPHA_OPAQUE 255
#define C_TRANS 0 #define C_TRANS 0


string decode_gif_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg) { string decode_gif_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg, char ** metadata) {


gifReadCbData buffinf = {(unsigned char *) buffer, size, 0}; gifReadCbData buffinf = {(unsigned char *) buffer, size, 0};
GifFileType * gif = NULL; GifFileType * gif = NULL;
Expand Down Expand Up @@ -70,6 +70,10 @@ string decode_gif_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg
return GifErrorString(errcode); return GifErrorString(errcode);
} }


// TODO: implement getting metadata from GIFs; this is a placeholder
*metadata = (char *)malloc(sizeof(char));
*metadata[0] = '\0';

return ""; return "";
} }


Expand Down
6 changes: 5 additions & 1 deletion src/decoder/jpeg_decoder.cpp
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "decoder.h" #include "decoder.h"


string decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg) { string decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg, char ** metadata) {
struct jpeg_decompress_struct cinfo; struct jpeg_decompress_struct cinfo;
struct lwip_jpeg_error_mgr jerr; struct lwip_jpeg_error_mgr jerr;


Expand Down Expand Up @@ -53,5 +53,9 @@ string decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** cim
jpeg_finish_decompress(&cinfo); jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&cinfo);


// TODO: implement getting metadata from GIFs; this is a placeholder

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment should this:
// TODO: implement getting metadata from JPEGs; this is a placeholder

*metadata = (char *)malloc(sizeof(char));
*metadata[0] = '\0';

return ""; return "";
} }
17 changes: 16 additions & 1 deletion src/decoder/png_decoder.cpp
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "decoder.h" #include "decoder.h"


string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg) { string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg, char ** metadata) {

// check it's a valid png buffer // check it's a valid png buffer
if (size < 8 || png_sig_cmp((png_const_bytep) buffer, 0, 8)) { if (size < 8 || png_sig_cmp((png_const_bytep) buffer, 0, 8)) {
return "Invalid PNG buffer"; return "Invalid PNG buffer";
Expand Down Expand Up @@ -43,6 +44,20 @@ string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** cimg
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
NULL, NULL, NULL); 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);

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);
break; //TODO: handle multiple lwip_data text chunks?
}
}

if (color_type == PNG_COLOR_TYPE_PALETTE) { if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr); png_set_palette_to_rgb(png_ptr);
color_type = PNG_COLOR_TYPE_RGB; color_type = PNG_COLOR_TYPE_RGB;
Expand Down
2 changes: 2 additions & 0 deletions src/encoder/encoder.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker {
int compression, int compression,
bool interlaced, bool interlaced,
bool trans, bool trans,
char * metadata,
NanCallback * callback NanCallback * callback
); );
~EncodeToPngBufferWorker(); ~EncodeToPngBufferWorker();
Expand All @@ -65,6 +66,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker {
int _compression; int _compression;
bool _interlaced; bool _interlaced;
bool _trans; bool _trans;
char * _metadata;
char * _pngbuf; char * _pngbuf;
size_t _pngbufsize; size_t _pngbufsize;
}; };
Expand Down
16 changes: 14 additions & 2 deletions src/encoder/init.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ NAN_METHOD(encodeToJpegBuffer) {
NanReturnUndefined(); NanReturnUndefined();
} }


// encoder.png(pixbuf, width, height, compression, interlaced, trans, callback) // encoder.png(pixbuf, width, height, compression, interlaced, trans, metadata, callback)
NAN_METHOD(encodeToPngBuffer) { NAN_METHOD(encodeToPngBuffer) {
NanScope(); NanScope();


Expand All @@ -31,7 +31,18 @@ NAN_METHOD(encodeToPngBuffer) {
int compression = args[3].As<Integer>()->Value(); int compression = args[3].As<Integer>()->Value();
bool interlaced = args[4]->BooleanValue(); bool interlaced = args[4]->BooleanValue();
bool trans = args[5]->BooleanValue(); bool trans = args[5]->BooleanValue();
NanCallback * callback = new NanCallback(args[6].As<Function>());
char * metadata;

if (args[6]->IsNull() || args[6]->IsUndefined()) {
metadata = NULL;
} else {
int metadata_len = args[6].As<String>()->Utf8Length();
metadata = (char *)malloc((metadata_len + 1) * sizeof(char));
args[6].As<String>()->WriteUtf8(metadata);
}

NanCallback * callback = new NanCallback(args[7].As<Function>());


NanAsyncQueueWorker( NanAsyncQueueWorker(
new EncodeToPngBufferWorker( new EncodeToPngBufferWorker(
Expand All @@ -41,6 +52,7 @@ NAN_METHOD(encodeToPngBuffer) {
compression, compression,
interlaced, interlaced,
trans, trans,
metadata,
callback callback
) )
); );
Expand Down
14 changes: 12 additions & 2 deletions src/encoder/png_worker.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker(
int compression, int compression,
bool interlaced, bool interlaced,
bool trans, bool trans,
char * metadata,
NanCallback * callback NanCallback * callback
): NanAsyncWorker(callback), _width(width), _height(height), ): NanAsyncWorker(callback), _width(width), _height(height),
_compression(compression), _interlaced(interlaced), _trans(trans), _compression(compression), _interlaced(interlaced), _trans(trans), _metadata(metadata),
_pngbuf(NULL), _pngbufsize(0) { _pngbuf(NULL), _pngbufsize(0) {
SaveToPersistent("buff", buff); // make sure buff isn't GC'ed SaveToPersistent("buff", buff); // make sure buff isn't GC'ed
_pixbuf = (unsigned char *) Buffer::Data(buff); _pixbuf = (unsigned char *) Buffer::Data(buff);
Expand Down Expand Up @@ -97,6 +98,15 @@ void EncodeToPngBufferWorker::Execute () {
PNG_COMPRESSION_TYPE_DEFAULT, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_DEFAULT
); );

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);
}

png_set_compression_level(png_ptr, compLevel); png_set_compression_level(png_ptr, compLevel);


pngWriteCbData buffinf = {NULL, 0}; pngWriteCbData buffinf = {NULL, 0};
Expand Down Expand Up @@ -164,4 +174,4 @@ void pngWriteCB(png_structp png_ptr, png_bytep data, png_size_t length) {


memcpy(buffinf->buff + buffinf->buffsize, data, length); memcpy(buffinf->buff + buffinf->buffsize, data, length);
buffinf->buffsize += length; buffinf->buffsize += length;
} }
Loading