Skip to content

Commit

Permalink
Merge pull request #165 from joannajw/png-metadata
Browse files Browse the repository at this point in the history
Support for reading and writing PNG metadata
  • Loading branch information
EyalAr committed Aug 7, 2015
2 parents e220e53 + 112332a commit d0a2631
Show file tree
Hide file tree
Showing 22 changed files with 277 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
npm-debug.log
tests/results
.idea
tests/.DS_Store
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

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
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

#### 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. `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

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 Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

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

// EXPORTS
Expand Down
14 changes: 14 additions & 0 deletions lib/ImagePrototypeInit.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@
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;
return data;
};

Image.prototype.width = function() {
return this.__lwip.width();
};
Expand Down Expand Up @@ -558,6 +571,7 @@
params.compression,
params.interlaced,
params.transparency === 'auto' ? that.__trans : params.transparency,
that.__metadata,
encoderCb
);
} else if (type === 'gif') {
Expand Down
8 changes: 4 additions & 4 deletions lib/obtain.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
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));
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) {
Expand All @@ -44,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");
Expand Down
23 changes: 19 additions & 4 deletions src/decoder/buffer_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DecodeBufferWorker::DecodeBufferWorker(
Local<Object> & 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);
Expand All @@ -16,7 +16,10 @@ DecodeBufferWorker::~DecodeBufferWorker() {}
void DecodeBufferWorker::Execute () {
CImg<unsigned char> * img = NULL;
string err;
err = _decoder(_buffer, _buffsize, &img);
char * metadata = NULL;

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

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

delete img;
return;
}

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

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

Local<Value> argv[] = {
NanNull(),
NanBufferUse(
Expand All @@ -48,7 +61,9 @@ void DecodeBufferWorker::HandleOKCallback () {
NanNew<Number>(_width),
NanNew<Number>(_height),
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 Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ using namespace v8;
using namespace node;
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 {
public:
Expand All @@ -46,6 +46,7 @@ class DecodeBufferWorker : public NanAsyncWorker {
size_t _height;
int _channels;
bool _trans; // transparency
char * _metadata;
};

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 decode_jpeg_buffer(char * buffer, size_t size, CImg<unsigned char> ** img);
string decode_png_buffer(char * buffer, size_t size, CImg<unsigned char> ** img);
string decode_gif_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, char ** metadata);
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);
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 Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#define ALPHA_OPAQUE 255
#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};
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);
}

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

return "";
}

Expand Down
6 changes: 5 additions & 1 deletion src/decoder/jpeg_decoder.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#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 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_destroy_decompress(&cinfo);

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

return "";
}
17 changes: 16 additions & 1 deletion src/decoder/png_decoder.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#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
if (size < 8 || png_sig_cmp((png_const_bytep) buffer, 0, 8)) {
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,
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) {
png_set_palette_to_rgb(png_ptr);
color_type = PNG_COLOR_TYPE_RGB;
Expand Down
2 changes: 2 additions & 0 deletions src/encoder/encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker {
int compression,
bool interlaced,
bool trans,
char * metadata,
NanCallback * callback
);
~EncodeToPngBufferWorker();
Expand All @@ -65,6 +66,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker {
int _compression;
bool _interlaced;
bool _trans;
char * _metadata;
char * _pngbuf;
size_t _pngbufsize;
};
Expand Down
16 changes: 14 additions & 2 deletions src/encoder/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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();

Expand All @@ -31,7 +31,18 @@ NAN_METHOD(encodeToPngBuffer) {
int compression = args[3].As<Integer>()->Value();
bool interlaced = args[4]->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(
new EncodeToPngBufferWorker(
Expand All @@ -41,6 +52,7 @@ NAN_METHOD(encodeToPngBuffer) {
compression,
interlaced,
trans,
metadata,
callback
)
);
Expand Down
14 changes: 12 additions & 2 deletions src/encoder/png_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker(
int compression,
bool interlaced,
bool trans,
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);
Expand Down Expand Up @@ -97,6 +98,15 @@ void EncodeToPngBufferWorker::Execute () {
PNG_COMPRESSION_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);

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);
buffinf->buffsize += length;
}
}
Loading

0 comments on commit d0a2631

Please sign in to comment.