Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 747add871983de7f39fd6f91a877491f652f087e @mash mash committed Jul 23, 2012
Showing with 439 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +74 −0 README.markdown
  3. +26 −0 binding.gyp
  4. +1 −0 index.js
  5. +26 −0 package.json
  6. +143 −0 src/imagemagick.cc
  7. +7 −0 src/imagemagick.h
  8. +27 −0 test/leak.js
  9. BIN test/test.jpg
  10. +133 −0 test/test.js
  11. BIN test/test.png
  12. BIN test/test.wide.png
@@ -0,0 +1,2 @@
+build
+node_modules/
@@ -0,0 +1,74 @@
+# node-imagemagick-native
+
+[Imagemagick](http://www.imagemagick.org/)'s [Magick++](http://www.imagemagick.org/Magick++/) binding for [Node](http://nodejs.org/).
+
+## Example
+
+ var imagemagick = require('imagemagick-native')
+ , srcData = require('fs').readFileSync('./test/test.png');
+
+ // returns a Buffer instance
+ var resizedBuffer = imagemagick.convert({
+ srcData: srcData, // provide a Buffer instance
+ width: 100,
+ height: 100,
+ resizeStyle: "aspectfill",
+ quality: 80,
+ format: 'JPEG'
+ });
+
+ require('fs').writeFileSync('./test/out.png', resizedBuffer, 'binary');
+
+## API
+
+### convert( options )
+
+Convert a buffer provided as `options.srcData` and return a Buffer.
+
+The `options` argument can have following values:
+
+ {
+ srcData: required. Buffer with binary image data
+ quality: optional. 0-100 integer, default 75. JPEG/MIFF/PNG compression level.
+ width: optional. px.
+ height: optional. px.
+ resizeStyle: optional. default: "aspectfill". can be "aspectfit", "fill"
+ aspectfill: keep aspect ratio, get the exact provided size,
+ crop top/bottom or left/right if necessary
+ aspectfit: keep aspect ratio, get maximum image that fits inside provided size
+ fill: forget aspect ratio, get the exact provided size
+ format: optional. one of http://www.imagemagick.org/script/formats.php ex: "JPEG"
+ debug: optional. 1 or 0
+ }
+
+This library currently provide only this, please try [node-imagemagick](https://github.com/rsms/node-imagemagick/) if you want more.
+
+## Performance - simple thumbnail creation
+
+ imagemagick: 34.58ms per iteration
+ imagemagick-native: 2.49ms per iteration
+
+See `node test/benchmark.js` for details.
+
+
+## License (MIT)
+
+Copyright (c) Masakazu Ohtsuka <http://maaash.jp/>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,26 @@
+{
+ "targets": [
+ {
+ "target_name": "imagemagick",
+ "sources": [ "src/imagemagick.cc" ],
+ "link_settings": {
+ "libraries": [
+ "-lMagick++",
+ ],
+ },
+ "conditions": [
+ ['OS=="mac"', {
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [
+ '<!@(Magick-config --cflags)'
+ ]
+ }
+ }, {
+ 'cflags': [
+ '<!@(Magick-config --cflags)'
+ ],
+ }]
+ ]
+ }
+ ]
+}
@@ -0,0 +1 @@
+module.exports = require(__dirname + '/build/Release/imagemagick.node');
@@ -0,0 +1,26 @@
+{
+ "name": "imagemagick-native",
+ "description": "ImageMagick's Magick++ bindings for NodeJS",
+ "keywords": ["imagemagick", "magick++", "resize", "convert"],
+ "version": "0.1.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/mash/node-imagemagick-native.git"
+ },
+ "author": "Masakazu Ohtsuka <o.masakazu@gmail.com> (http://maaash.jp/)",
+ "contributors": [
+ ],
+ "main": "./index.js",
+ "scripts": {
+ "test": "node test/test.js",
+ "install": "node-gyp rebuild"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "dependencies": {
+ },
+ "devDependencies": {
+ "tap": "*"
+ }
+}
@@ -0,0 +1,143 @@
+#include "imagemagick.h"
+#include <list>
+
+// input
+// args[ 0 ]: options. required, object with following key,values
+// {
+// srcData: required. Buffer with binary image data
+// quality: optional. 0-100 integer, default 75. JPEG/MIFF/PNG compression level.
+// width: optional. px.
+// height: optional. px.
+// resizeStyle: optional. default: "aspectfill". can be "aspectfit", "fill"
+// format: optional. one of http://www.imagemagick.org/script/formats.php ex: "JPEG"
+// debug: optional. 1 or 0
+// }
+Handle<Value> Convert(const Arguments& args) {
+ HandleScope scope;
+
+ if ( args.Length() != 1 ) {
+ return THROW_ERROR_EXCEPTION("convert() requires 1 (option) argument!");
+ }
+ if ( ! args[ 0 ]->IsObject() ) {
+ return THROW_ERROR_EXCEPTION("convert()'s 1st argument should be an object");
+ }
+ Local<Object> obj = Local<Object>::Cast( args[ 0 ] );
+
+ Local<Object> srcData = Local<Object>::Cast( obj->Get( String::NewSymbol("srcData") ) );
+ if ( srcData->IsUndefined() || ! node::Buffer::HasInstance(srcData) ) {
+ return THROW_ERROR_EXCEPTION("convert()'s 1st argument should have \"srcData\" key with a Buffer instance");
+ }
+
+ int debug = obj->Get( String::NewSymbol("debug") )->Uint32Value();
+ if (debug) printf( "debug: on\n" );
+
+ Magick::Blob srcBlob( node::Buffer::Data(srcData), node::Buffer::Length(srcData) );
+ Magick::Image image( srcBlob );
+ if (debug) printf("original width,height: %d, %d\n", (int) image.columns(), (int) image.rows());
+
+ unsigned int width = obj->Get( String::NewSymbol("width") )->Uint32Value();
+ if (debug) printf( "width: %d\n", width );
+
+ unsigned int height = obj->Get( String::NewSymbol("height") )->Uint32Value();
+ if (debug) printf( "height: %d\n", height );
+
+ Local<Value> resizeStyleValue = obj->Get( String::NewSymbol("resizeStyle") );
+ const char* resizeStyle = "aspectfill";
+ if ( ! resizeStyleValue->IsUndefined() ) {
+ String::AsciiValue resizeStyleAsciiValue( resizeStyleValue->ToString() );
+ resizeStyle = *resizeStyleAsciiValue;
+ }
+ if (debug) printf( "resizeStyle: %s\n", resizeStyle );
+
+ if ( width || height ) {
+ if ( ! width ) { width = image.columns(); }
+ if ( ! height ) { height = image.rows(); }
+
+ // do resize
+ if ( strcmp( resizeStyle, "aspectfill" ) == 0 ) {
+ // ^ : Fill Area Flag ('^' flag)
+ // is not implemented in Magick++
+ // and gravity: center, extent doesnt look like working as exptected
+ // so we do it ourselves
+
+ // keep aspect ratio, get the exact provided size, crop top/bottom or left/right if necessary
+ double aspectratioExpected = (double)height / (double)width;
+ double aspectratioOriginal = (double)image.rows() / (double)image.columns();
+ unsigned int xoffset = 0;
+ unsigned int yoffset = 0;
+ unsigned int resizewidth;
+ unsigned int resizeheight;
+ if ( aspectratioExpected > aspectratioOriginal ) {
+ // expected is taller
+ resizewidth = (unsigned int)( (double)height / (double)image.rows() * (double)image.columns() + 1. );
+ resizeheight = height;
+ xoffset = (unsigned int)( (resizewidth - width) / 2. );
+ yoffset = 0;
+ }
+ else {
+ // expected is wider
+ resizewidth = width;
+ resizeheight = (unsigned int)( (double)width / (double)image.columns() * (double)image.rows() + 1. );
+ xoffset = 0;
+ yoffset = (unsigned int)( (resizeheight - height) / 2. );
+ }
+
+ if (debug) printf( "resize to: %d, %d\n", resizewidth, resizeheight );
+ Magick::Geometry resizeGeometry( resizewidth, resizeheight, 0, 0, 0, 0 );
+ image.resize( resizeGeometry );
+
+ if (debug) printf( "crop to: %d, %d, %d, %d\n", width, height, xoffset, yoffset );
+ Magick::Geometry cropGeometry( width, height, xoffset, yoffset, 0, 0 );
+ image.extent( cropGeometry );
+ }
+ else if ( strcmp ( resizeStyle, "aspectfit" ) == 0 ) {
+ // keep aspect ratio, get the maximum image which fits inside specified size
+ char geometryString[ 32 ];
+ sprintf( geometryString, "%dx%d", width, height );
+ if (debug) printf( "resize to: %s\n", geometryString );
+ image.resize( geometryString );
+ }
+ else if ( strcmp ( resizeStyle, "fill" ) == 0 ) {
+ // change aspect ratio and fill specified size
+ char geometryString[ 32 ];
+ sprintf( geometryString, "%dx%d!", width, height );
+ if (debug) printf( "resize to: %s\n", geometryString );
+ image.resize( geometryString );
+ }
+ else {
+ return THROW_ERROR_EXCEPTION("resizeStyle not supported");
+ }
+ if (debug) printf( "resized to: %d, %d\n", (int)image.columns(), (int)image.rows() );
+ }
+
+ unsigned int quality = obj->Get( String::NewSymbol("quality") )->Uint32Value();
+ if ( quality ) {
+ if (debug) printf( "quality: %d\n", quality );
+ image.quality( quality );
+ }
+
+ Local<Value> formatValue = obj->Get( String::NewSymbol("format") );
+ if ( ! formatValue->IsUndefined() ) {
+ String::AsciiValue format( formatValue->ToString() );
+ if (debug) printf( "format: %s\n", *format );
+ image.magick( *format );
+ }
+
+ // make background white when converting transparent png to jpg
+ std::list<Magick::Image> imageList( 1, image );
+ Magick::flattenImages( &image, imageList.begin(), imageList.end() );
+ imageList.clear();
+
+ Magick::Blob dstBlob;
+ image.write( &dstBlob );
+
+ node::Buffer* retBuffer = node::Buffer::New( dstBlob.length() );
+ memcpy( node::Buffer::Data( retBuffer->handle_ ), dstBlob.data(), dstBlob.length() );
+ return scope.Close( retBuffer->handle_ );
+}
+
+void init(Handle<Object> target) {
+ NODE_SET_METHOD(target, "convert", Convert);
+}
+
+NODE_MODULE(imagemagick, init);
@@ -0,0 +1,7 @@
+#include <node.h>
+#include <node_buffer.h>
+#include <Magick++.h>
+
+using namespace v8;
+
+#define THROW_ERROR_EXCEPTION(x) ThrowException(v8::Exception::Error(String::New(x)))
@@ -0,0 +1,27 @@
+var imagemagick = require('..')
+, debug = 1
+, memwatch = require('memwatch')
+;
+
+memwatch.on( 'leak', function( info ) {
+ console.log( "leak: ", info );
+});
+memwatch.on('stats', function(stats) {
+ console.log( "stats: ", stats );
+});
+var srcData = require('fs').readFileSync( "./test/test.jpg" );
+
+var hd = new memwatch.HeapDiff();
+
+var ret = imagemagick.convert({
+ srcData: srcData,
+ width: 100,
+ height: 100,
+ resizeStyle: "fill",
+ format: 'JPEG',
+ debug: debug
+});
+require('assert').ok( ret );
+
+var diff = hd.end();
+console.log("diff: ", diff );
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.

0 comments on commit 747add8

Please sign in to comment.