Skip to content

Commit 10a82ec

Browse files
committed
support reading CMYK, YCCK JPEGs
Fixes #425
1 parent 2019b08 commit 10a82ec

File tree

6 files changed

+75
-29
lines changed

6 files changed

+75
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ canvas.createJPEGStream() // new
8787
* Provide better, Node.js core-style coded errors for failed sys calls. (For
8888
example, provide an error with code 'ENOENT' if setting `img.src` to a path
8989
that does not exist.)
90+
* Support reading CMYK, YCCK JPEGs.
9091

9192
### Added
9293
* Prebuilds (#992) with different libc versions to the prebuilt binary (#1140)

src/Image.cc

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -777,16 +777,27 @@ static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
777777

778778
#endif
779779

780+
void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) {
781+
int stride = naturalWidth * 4;
782+
for (int y = 0; y < naturalHeight; ++y) {
783+
jpeg_read_scanlines(args, &src, 1);
784+
uint32_t *row = (uint32_t*)(data + stride * y);
785+
for (int x = 0; x < naturalWidth; ++x) {
786+
int bx = args->output_components * x;
787+
row[x] = decode(src + bx);
788+
}
789+
}
790+
}
791+
780792
/*
781793
* Takes an initialised jpeg_decompress_struct and decodes the
782794
* data into _surface.
783795
*/
784796

785797
cairo_status_t
786798
Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
787-
int stride = naturalWidth * 4;
788-
cairo_status_t status;
789-
799+
cairo_status_t status = CAIRO_STATUS_SUCCESS;
800+
790801
uint8_t *data = (uint8_t *) malloc(naturalWidth * naturalHeight * 4);
791802
if (!data) {
792803
jpeg_abort_decompress(args);
@@ -804,33 +815,44 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
804815
return CAIRO_STATUS_NO_MEMORY;
805816
}
806817

807-
for (int y = 0; y < naturalHeight; ++y) {
808-
jpeg_read_scanlines(args, &src, 1);
809-
uint32_t *row = (uint32_t *)(data + stride * y);
810-
for (int x = 0; x < naturalWidth; ++x) {
811-
if (args->jpeg_color_space == 1) {
812-
uint32_t *pixel = row + x;
813-
*pixel = 255 << 24
814-
| src[x] << 16
815-
| src[x] << 8
816-
| src[x];
817-
} else {
818-
int bx = 3 * x;
819-
uint32_t *pixel = row + x;
820-
*pixel = 255 << 24
821-
| src[bx + 0] << 16
822-
| src[bx + 1] << 8
823-
| src[bx + 2];
824-
}
825-
}
818+
// These are the three main cases to handle. libjpeg converts YCCK to CMYK
819+
// and YCbCr to RGB by default.
820+
switch (args->out_color_space) {
821+
case JCS_CMYK:
822+
jpegToARGB(args, data, src, [](uint8_t const* src) {
823+
uint16_t k = static_cast<uint16_t>(src[3]);
824+
uint8_t r = k * src[0] / 255;
825+
uint8_t g = k * src[1] / 255;
826+
uint8_t b = k * src[2] / 255;
827+
return 255 << 24 | r << 16 | g << 8 | b;
828+
});
829+
break;
830+
case JCS_RGB:
831+
jpegToARGB(args, data, src, [](uint8_t const* src) {
832+
uint8_t r = src[0], g = src[1], b = src[2];
833+
return 255 << 24 | r << 16 | g << 8 | b;
834+
});
835+
break;
836+
case JCS_GRAYSCALE:
837+
jpegToARGB(args, data, src, [](uint8_t const* src) {
838+
uint8_t v = src[0];
839+
return 255 << 24 | v << 16 | v << 8 | v;
840+
});
841+
break;
842+
default:
843+
this->errorInfo.set("Unsupported JPEG encoding");
844+
status = CAIRO_STATUS_READ_ERROR;
845+
break;
826846
}
827847

828-
_surface = cairo_image_surface_create_for_data(
829-
data
830-
, CAIRO_FORMAT_ARGB32
831-
, naturalWidth
832-
, naturalHeight
833-
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
848+
if (!status) {
849+
_surface = cairo_image_surface_create_for_data(
850+
data
851+
, CAIRO_FORMAT_ARGB32
852+
, naturalWidth
853+
, naturalHeight
854+
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
855+
}
834856

835857
jpeg_finish_decompress(args);
836858
jpeg_destroy_decompress(args);

src/Image.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "Canvas.h"
1212
#include "CanvasError.h"
13+
#include <functional>
1314

1415
#ifdef HAVE_JPEG
1516
#include <jpeglib.h>
@@ -34,7 +35,7 @@
3435
#endif
3536
#endif
3637

37-
38+
using JPEGDecodeL = std::function<uint32_t (uint8_t* const src)>;
3839

3940
class Image: public Nan::ObjectWrap {
4041
public:
@@ -81,6 +82,7 @@ class Image: public Nan::ObjectWrap {
8182
#ifdef HAVE_JPEG
8283
cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
8384
cairo_status_t loadJPEG(FILE *stream);
85+
void jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode);
8486
cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info);
8587
#if CAIRO_VERSION_MINOR >= 10
8688
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);

test/fixtures/grayscale.jpg

17.5 KB
Loading

test/fixtures/ycck.jpg

115 KB
Loading

test/public/tests.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,27 @@ tests['drawImage(img) jpeg'] = function (ctx, done) {
16671667
img.src = imageSrc('face.jpeg')
16681668
}
16691669

1670+
tests['drawImage(img) YCCK JPEG (#425)'] = function (ctx, done) {
1671+
// This also provides coverage for CMYK JPEGs
1672+
var img = new Image()
1673+
img.onload = function () {
1674+
ctx.drawImage(img, 0, 0, 100, 100)
1675+
done(null)
1676+
}
1677+
img.onerror = done
1678+
img.src = imageSrc('ycck.jpg')
1679+
}
1680+
1681+
tests['drawImage(img) grayscale JPEG'] = function (ctx, done) {
1682+
var img = new Image()
1683+
img.onload = function () {
1684+
ctx.drawImage(img, 0, 0, 100, 100)
1685+
done(null)
1686+
}
1687+
img.onerror = done
1688+
img.src = imageSrc('grayscale.jpg')
1689+
}
1690+
16701691
tests['drawImage(img) svg'] = function (ctx, done) {
16711692
var img = new Image()
16721693
img.onload = function () {

0 commit comments

Comments
 (0)