diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 0c174941dbf5..98eac6b2ff68 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -12,7 +12,6 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/base64.h" #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" @@ -21,6 +20,7 @@ #include "net/base/data_url.h" #include "third_party/skia/include/core/SkPixelRef.h" #include "ui/base/layout.h" +#include "ui/base/webui/web_ui_util.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/geometry/size.h" @@ -76,6 +76,15 @@ float GetScaleFactorFromPath(const base::FilePath& path) { return 1.0f; } +// Get the scale factor from options object at the first argument +float GetScaleFactorFromOptions(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); + return scale_factor; +} + bool AddImageSkiaRep(gfx::ImageSkia* image, const unsigned char* data, size_t size, @@ -230,21 +239,40 @@ HICON NativeImage::GetHICON(int size) { } #endif -v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { - scoped_refptr png = image_.As1xPNGBytes(); - return node::Buffer::Copy(isolate, - reinterpret_cast(png->front()), - static_cast(png->size())).ToLocalChecked(); +v8::Local NativeImage::ToPNG(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) { + const char* data = reinterpret_cast(png->front()); + size_t size = png->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); + } + } + + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + std::unique_ptr> encoded( + new std::vector()); + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, encoded.get()); + const char* data = reinterpret_cast(encoded->data()); + size_t size = encoded->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); } -v8::Local NativeImage::ToBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::ToBitmap(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::Copy(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::Copy(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize()).ToLocalChecked(); + bitmap.getSafeSize()).ToLocalChecked(); } v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { @@ -253,26 +281,34 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { return node::Buffer::Copy( isolate, reinterpret_cast(&output.front()), - static_cast(output.size())).ToLocalChecked(); + output.size()).ToLocalChecked(); } -std::string NativeImage::ToDataURL() { - scoped_refptr png = image_.As1xPNGBytes(); - std::string data_url; - data_url.insert(data_url.end(), png->front(), png->front() + png->size()); - base::Base64Encode(data_url, &data_url); - data_url.insert(0, "data:image/png;base64,"); - return data_url; +std::string NativeImage::ToDataURL(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) + return webui::GetPngDataUrl(png->front(), png->size()); + } + + return webui::GetBitmapDataUrl( + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap()); } -v8::Local NativeImage::GetBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::GetBitmap(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::New(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::New(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize(), + bitmap.getSafeSize(), &Noop, nullptr).ToLocalChecked(); } diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index ee1b5f5d4b81..a6614a81b0ec 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -70,10 +70,10 @@ class NativeImage : public mate::Wrappable { ~NativeImage() override; private: - v8::Local ToPNG(v8::Isolate* isolate); + v8::Local ToPNG(mate::Arguments* args); v8::Local ToJPEG(v8::Isolate* isolate, int quality); - v8::Local ToBitmap(v8::Isolate* isolate); - v8::Local GetBitmap(v8::Isolate* isolate); + v8::Local ToBitmap(mate::Arguments* args); + v8::Local GetBitmap(mate::Arguments* args); v8::Local GetNativeHandle( v8::Isolate* isolate, mate::Arguments* args); @@ -81,7 +81,7 @@ class NativeImage : public mate::Wrappable { const base::DictionaryValue& options); mate::Handle Crop(v8::Isolate* isolate, const gfx::Rect& rect); - std::string ToDataURL(); + std::string ToDataURL(mate::Arguments* args); bool IsEmpty(); gfx::Size GetSize(); float GetAspectRatio(); diff --git a/docs/api/native-image.md b/docs/api/native-image.md index cc910cbb8a98..0350f13bc902 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -165,7 +165,10 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer The following methods are available on instances of the `NativeImage` class: -#### `image.toPNG()` +#### `image.toPNG([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded data. @@ -175,16 +178,25 @@ Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded da Returns `Buffer` - A [Buffer][buffer] that contains the image's `JPEG` encoded data. -#### `image.toBitmap()` +#### `image.toBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel data. -#### `image.toDataURL()` +#### `image.toDataURL([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `String` - The data URL of the image. -#### `image.getBitmap()` +#### `image.getBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data. diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index e28b37bebfe6..22fe187b2d74 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -11,11 +11,15 @@ describe('nativeImage module', () => { assert.equal(empty.isEmpty(), true) assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.toDataURL(), 'data:image/png;base64,') + assert.equal(empty.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,') assert.deepEqual(empty.getSize(), {width: 0, height: 0}) assert.deepEqual(empty.getBitmap(), []) + assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toBitmap(), []) + assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toJPEG(100), []) assert.deepEqual(empty.toPNG(), []) + assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), []) if (process.platform === 'darwin') { assert.deepEqual(empty.getNativeHandle(), []) @@ -79,6 +83,60 @@ describe('nativeImage module', () => { }) }) + describe('toDataURL()', () => { + it('returns a PNG data URL', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) + assert.equal(imageA.toDataURL(), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(imageA.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + }) + + it('returns a data URL at 1x scale factor by default', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + + const imageC = nativeImage.createFromDataURL(imageB.toDataURL()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 2.0})) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + }) + }) + + describe('toPNG()', () => { + it('returns a buffer at 1x scale factor by default', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + + const imageC = nativeImage.createFromBuffer(imageB.toPNG()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0}) + assert.deepEqual(imageC.getSize(), {width: 269, height: 95}) + }) + }) + describe('createFromPath(path)', () => { it('returns an empty image for invalid paths', () => { assert(nativeImage.createFromPath('').isEmpty()) diff --git a/spec/fixtures/assets/1x1.png b/spec/fixtures/assets/1x1.png new file mode 100644 index 000000000000..427fb75b8628 Binary files /dev/null and b/spec/fixtures/assets/1x1.png differ