Merge pull request #8849 from electron/to-data-url-scale-factor

Support scale factor in more NativeImage APIs
This commit is contained in:
Kevin Sawicki 2017-03-07 12:55:56 -08:00 committed by GitHub
commit 4f817873f1
5 changed files with 140 additions and 34 deletions

View file

@ -12,7 +12,6 @@
#include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/native_mate_converters/value_converter.h"
#include "base/base64.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/strings/pattern.h" #include "base/strings/pattern.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
@ -21,6 +20,7 @@
#include "net/base/data_url.h" #include "net/base/data_url.h"
#include "third_party/skia/include/core/SkPixelRef.h" #include "third_party/skia/include/core/SkPixelRef.h"
#include "ui/base/layout.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/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h" #include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
@ -76,6 +76,15 @@ float GetScaleFactorFromPath(const base::FilePath& path) {
return 1.0f; 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, bool AddImageSkiaRep(gfx::ImageSkia* image,
const unsigned char* data, const unsigned char* data,
size_t size, size_t size,
@ -230,21 +239,40 @@ HICON NativeImage::GetHICON(int size) {
} }
#endif #endif
v8::Local<v8::Value> NativeImage::ToPNG(v8::Isolate* isolate) { v8::Local<v8::Value> NativeImage::ToPNG(mate::Arguments* args) {
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes(); float scale_factor = GetScaleFactorFromOptions(args);
return node::Buffer::Copy(isolate,
reinterpret_cast<const char*>(png->front()), if (scale_factor == 1.0f) {
static_cast<size_t>(png->size())).ToLocalChecked(); // Use raw 1x PNG bytes when available
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
if (png->size() > 0) {
const char* data = reinterpret_cast<const char*>(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<std::vector<unsigned char>> encoded(
new std::vector<unsigned char>());
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, encoded.get());
const char* data = reinterpret_cast<char*>(encoded->data());
size_t size = encoded->size();
return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked();
} }
v8::Local<v8::Value> NativeImage::ToBitmap(v8::Isolate* isolate) { v8::Local<v8::Value> NativeImage::ToBitmap(mate::Arguments* args) {
if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); float scale_factor = GetScaleFactorFromOptions(args);
const SkBitmap* bitmap = image_.ToSkBitmap(); const SkBitmap bitmap =
SkPixelRef* ref = bitmap->pixelRef(); image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap();
return node::Buffer::Copy(isolate, SkPixelRef* ref = bitmap.pixelRef();
if (!ref)
return node::Buffer::New(args->isolate(), 0).ToLocalChecked();
return node::Buffer::Copy(args->isolate(),
reinterpret_cast<const char*>(ref->pixels()), reinterpret_cast<const char*>(ref->pixels()),
bitmap->getSafeSize()).ToLocalChecked(); bitmap.getSafeSize()).ToLocalChecked();
} }
v8::Local<v8::Value> NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { v8::Local<v8::Value> NativeImage::ToJPEG(v8::Isolate* isolate, int quality) {
@ -253,26 +281,34 @@ v8::Local<v8::Value> NativeImage::ToJPEG(v8::Isolate* isolate, int quality) {
return node::Buffer::Copy( return node::Buffer::Copy(
isolate, isolate,
reinterpret_cast<const char*>(&output.front()), reinterpret_cast<const char*>(&output.front()),
static_cast<size_t>(output.size())).ToLocalChecked(); output.size()).ToLocalChecked();
} }
std::string NativeImage::ToDataURL() { std::string NativeImage::ToDataURL(mate::Arguments* args) {
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes(); float scale_factor = GetScaleFactorFromOptions(args);
std::string data_url;
data_url.insert(data_url.end(), png->front(), png->front() + png->size()); if (scale_factor == 1.0f) {
base::Base64Encode(data_url, &data_url); // Use raw 1x PNG bytes when available
data_url.insert(0, "data:image/png;base64,"); scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
return data_url; if (png->size() > 0)
return webui::GetPngDataUrl(png->front(), png->size());
}
return webui::GetBitmapDataUrl(
image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap());
} }
v8::Local<v8::Value> NativeImage::GetBitmap(v8::Isolate* isolate) { v8::Local<v8::Value> NativeImage::GetBitmap(mate::Arguments* args) {
if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); float scale_factor = GetScaleFactorFromOptions(args);
const SkBitmap* bitmap = image_.ToSkBitmap(); const SkBitmap bitmap =
SkPixelRef* ref = bitmap->pixelRef(); image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap();
return node::Buffer::New(isolate, SkPixelRef* ref = bitmap.pixelRef();
if (!ref)
return node::Buffer::New(args->isolate(), 0).ToLocalChecked();
return node::Buffer::New(args->isolate(),
reinterpret_cast<char*>(ref->pixels()), reinterpret_cast<char*>(ref->pixels()),
bitmap->getSafeSize(), bitmap.getSafeSize(),
&Noop, &Noop,
nullptr).ToLocalChecked(); nullptr).ToLocalChecked();
} }

View file

@ -70,10 +70,10 @@ class NativeImage : public mate::Wrappable<NativeImage> {
~NativeImage() override; ~NativeImage() override;
private: private:
v8::Local<v8::Value> ToPNG(v8::Isolate* isolate); v8::Local<v8::Value> ToPNG(mate::Arguments* args);
v8::Local<v8::Value> ToJPEG(v8::Isolate* isolate, int quality); v8::Local<v8::Value> ToJPEG(v8::Isolate* isolate, int quality);
v8::Local<v8::Value> ToBitmap(v8::Isolate* isolate); v8::Local<v8::Value> ToBitmap(mate::Arguments* args);
v8::Local<v8::Value> GetBitmap(v8::Isolate* isolate); v8::Local<v8::Value> GetBitmap(mate::Arguments* args);
v8::Local<v8::Value> GetNativeHandle( v8::Local<v8::Value> GetNativeHandle(
v8::Isolate* isolate, v8::Isolate* isolate,
mate::Arguments* args); mate::Arguments* args);
@ -81,7 +81,7 @@ class NativeImage : public mate::Wrappable<NativeImage> {
const base::DictionaryValue& options); const base::DictionaryValue& options);
mate::Handle<NativeImage> Crop(v8::Isolate* isolate, mate::Handle<NativeImage> Crop(v8::Isolate* isolate,
const gfx::Rect& rect); const gfx::Rect& rect);
std::string ToDataURL(); std::string ToDataURL(mate::Arguments* args);
bool IsEmpty(); bool IsEmpty();
gfx::Size GetSize(); gfx::Size GetSize();
float GetAspectRatio(); float GetAspectRatio();

View file

@ -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: 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. 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. 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 Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel
data. data.
#### `image.toDataURL()` #### `image.toDataURL([options])`
* `options` Object (optional)
* `scaleFactor` Double (optional) - Defaults to 1.0.
Returns `String` - The data URL of the image. 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. Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data.

View file

@ -11,11 +11,15 @@ describe('nativeImage module', () => {
assert.equal(empty.isEmpty(), true) assert.equal(empty.isEmpty(), true)
assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.getAspectRatio(), 1)
assert.equal(empty.toDataURL(), 'data:image/png;base64,') 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.getSize(), {width: 0, height: 0})
assert.deepEqual(empty.getBitmap(), []) assert.deepEqual(empty.getBitmap(), [])
assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), [])
assert.deepEqual(empty.toBitmap(), []) assert.deepEqual(empty.toBitmap(), [])
assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), [])
assert.deepEqual(empty.toJPEG(100), []) assert.deepEqual(empty.toJPEG(100), [])
assert.deepEqual(empty.toPNG(), []) assert.deepEqual(empty.toPNG(), [])
assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), [])
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
assert.deepEqual(empty.getNativeHandle(), []) 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)', () => { describe('createFromPath(path)', () => {
it('returns an empty image for invalid paths', () => { it('returns an empty image for invalid paths', () => {
assert(nativeImage.createFromPath('').isEmpty()) assert(nativeImage.createFromPath('').isEmpty())

BIN
spec/fixtures/assets/1x1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B