You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
7.8 KiB

14 years ago
//
// Image.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "Canvas.h"
#include "Image.h"
14 years ago
#include <stdlib.h>
#include <string.h>
#include <errno.h>
14 years ago
#ifdef HAVE_JPEG
#include <jpeglib.h>
#endif
Persistent<FunctionTemplate> Image::constructor;
14 years ago
/*
* Initialize Image.
*/
void
Image::Initialize(Handle<Object> target) {
HandleScope scope;
// Constructor
constructor = Persistent<FunctionTemplate>::New(FunctionTemplate::New(Image::New));
constructor->InstanceTemplate()->SetInternalFieldCount(1);
constructor->SetClassName(String::NewSymbol("Image"));
14 years ago
// Prototype
Local<ObjectTemplate> proto = constructor->PrototypeTemplate();
14 years ago
proto->SetAccessor(String::NewSymbol("src"), GetSrc, SetSrc);
proto->SetAccessor(String::NewSymbol("complete"), GetComplete);
proto->SetAccessor(String::NewSymbol("width"), GetWidth);
proto->SetAccessor(String::NewSymbol("height"), GetHeight);
proto->SetAccessor(String::NewSymbol("onload"), GetOnload, SetOnload);
proto->SetAccessor(String::NewSymbol("onerror"), GetOnerror, SetOnerror);
target->Set(String::NewSymbol("Image"), constructor->GetFunction());
14 years ago
}
/*
* Initialize a new Image.
*/
Handle<Value>
Image::New(const Arguments &args) {
HandleScope scope;
Image *img = new Image;
img->Wrap(args.This());
return args.This();
}
/*
* Get complete boolean.
*/
Handle<Value>
Image::GetComplete(Local<String>, const AccessorInfo &info) {
HandleScope scope;
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return scope.Close(Boolean::New(Image::COMPLETE == img->state));
}
/*
* Get width.
*/
Handle<Value>
Image::GetWidth(Local<String>, const AccessorInfo &info) {
HandleScope scope;
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return scope.Close(Number::New(img->width));
}
/*
* Get height.
*/
Handle<Value>
Image::GetHeight(Local<String>, const AccessorInfo &info) {
HandleScope scope;
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return scope.Close(Number::New(img->height));
}
/*
* Get src path.
*/
Handle<Value>
Image::GetSrc(Local<String>, const AccessorInfo &info) {
HandleScope scope;
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return scope.Close(String::New(img->filename));
}
/*
* Set src path.
*/
void
Image::SetSrc(Local<String>, Local<Value> val, const AccessorInfo &info) {
if (val->IsString()) {
String::AsciiValue src(val);
Image *img = ObjectWrap::Unwrap<Image>(info.This());
if (img->filename) free(img->filename);
img->filename = strdup(*src);
img->load();
}
}
/*
* Get onload callback.
*/
Handle<Value>
Image::GetOnload(Local<String>, const AccessorInfo &info) {
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return img->onload;
}
/*
* Set onload callback.
*/
void
Image::SetOnload(Local<String>, Local<Value> val, const AccessorInfo &info) {
if (val->IsFunction()) {
Image *img = ObjectWrap::Unwrap<Image>(info.This());
img->onload = Persistent<Function>::New(Handle<Function>::Cast(val));
}
}
/*
* Get onerror callback.
*/
Handle<Value>
Image::GetOnerror(Local<String>, const AccessorInfo &info) {
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return img->onerror;
}
/*
* Set onerror callback.
*/
void
Image::SetOnerror(Local<String>, Local<Value> val, const AccessorInfo &info) {
if (val->IsFunction()) {
Image *img = ObjectWrap::Unwrap<Image>(info.This());
img->onerror = Persistent<Function>::New(Handle<Function>::Cast(val));
}
}
14 years ago
/*
* Initialize a new Image.
*/
Image::Image() {
14 years ago
filename = NULL;
14 years ago
_surface = NULL;
width = height = 0;
state = DEFAULT;
14 years ago
}
/*
* Destroy image and associated surface.
*/
Image::~Image() {
if (_surface) {
V8::AdjustAmountOfExternalAllocatedMemory(-4 * width * height);
cairo_surface_destroy(_surface);
}
if (filename) free(filename);
}
14 years ago
/*
* Initiate image loading.
*/
void
Image::load() {
if (LOADING != state) {
state = LOADING;
loadSync();
}
}
/*
* Load image synchronously.
*/
void
Image::loadSync() {
TryCatch try_catch;
loadSurface();
if (try_catch.HasCaught()) {
error(try_catch);
} else {
V8::AdjustAmountOfExternalAllocatedMemory(4 * width * height);
loaded();
}
}
14 years ago
/*
* Invoke onload (when assigned) and assign dimensions.
14 years ago
*/
void
Image::loaded() {
HandleScope scope;
state = COMPLETE;
if (!onload.IsEmpty()) {
TryCatch try_catch;
onload->Call(Context::GetCurrent()->Global(), 0, NULL);
onload.Dispose();
if (try_catch.HasCaught()) {
error(try_catch);
}
}
}
14 years ago
/*
* Invoke onerror (when assigned) with the given err.
*/
void
Image::error(TryCatch &try_catch) {
14 years ago
HandleScope scope;
if (onerror.IsEmpty()) {
FatalException(try_catch);
} else {
Local<Value> argv[1] = { try_catch.Exception() };
TryCatch try_catch2;
onerror->Call(Context::GetCurrent()->Global(), 1, argv);
14 years ago
onerror.Dispose();
if (try_catch2.HasCaught()) {
FatalException(try_catch2);
}
}
}
14 years ago
/*
* Load cairo surface from the image src.
*
* TODO: better format detection
14 years ago
* TODO: support more formats
14 years ago
* TODO: use node IO or at least thread pool
14 years ago
*/
Handle<Value>
Image::loadSurface() {
switch (extension(filename)) {
case Image::PNG: return loadPNG();
#ifdef HAVE_JPEG
case Image::JPEG: return loadJPEG();
#endif
}
return ThrowException(Exception::Error(String::New("failed to detect image format")));
}
/*
* Load PNG.
*/
Handle<Value>
Image::loadPNG() {
_surface = cairo_image_surface_create_from_png(filename);
cairo_status_t status = cairo_surface_status(_surface);
if (status) {
return ThrowException(Canvas::Error(status));
} else {
width = cairo_image_surface_get_width(_surface);
height = cairo_image_surface_get_height(_surface);
return Undefined();
}
}
#ifdef HAVE_JPEG
/*
* Load JPEG, convert RGB to ARGB.
*/
Handle<Value>
Image::loadJPEG() {
FILE *stream = fopen(filename, "r");
14 years ago
// Generalized errors
if (!stream) {
return ThrowException(ErrnoException(errno, "fopen"));
}
// JPEG setup
struct jpeg_decompress_struct info;
struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info);
jpeg_stdio_src(&info, stream);
jpeg_read_header(&info, 1);
jpeg_start_decompress(&info);
width = info.output_width;
height = info.output_height;
// Data alloc
int stride = width * 4;
uint8_t *data = (uint8_t *) malloc(width * height * 4);
if (!data) return ThrowException(Canvas::Error(CAIRO_STATUS_NO_MEMORY));
uint8_t *src = (uint8_t *) malloc(width * 3);
if (!src) {
free(data);
return ThrowException(Canvas::Error(CAIRO_STATUS_NO_MEMORY));
}
// Copy RGB -> ARGB
for (int y = 0; y < height; ++y) {
jpeg_read_scanlines(&info, &src, 1);
uint32_t *row = (uint32_t *)(data + stride * y);
for (int x = 0; x < width; ++x) {
int bx = 3 * x;
uint32_t *pixel = row + x;
*pixel = 255 << 24
| src[bx + 0] << 16
| src[bx + 1] << 8
| src[bx + 2];
}
}
// New image surface
_surface = cairo_image_surface_create_for_data(
data
, CAIRO_FORMAT_ARGB32
, width
, height
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
// Cleanup
free(src);
fclose(stream);
jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info);
cairo_status_t status = cairo_surface_status(_surface);
if (status) {
free(data);
return ThrowException(Canvas::Error(status));
} else {
return Undefined();
}
}
#endif
/*
* Return UNKNOWN, JPEG, or PNG based on the filename.
*/
Image::type
Image::extension(const char *filename) {
size_t len = strlen(filename);
filename += len;
if (0 == strcmp(".jpeg", filename - 5)) return Image::JPEG;
if (0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
if (0 == strcmp(".png", filename - 4)) return Image::PNG;
return Image::UNKNOWN;
}