Merge pull request #8849 from electron/to-data-url-scale-factor
Support scale factor in more NativeImage APIs
This commit is contained in:
commit
4f817873f1
5 changed files with 140 additions and 34 deletions
|
@ -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) {
|
||||||
|
float scale_factor = GetScaleFactorFromOptions(args);
|
||||||
|
|
||||||
|
if (scale_factor == 1.0f) {
|
||||||
|
// Use raw 1x PNG bytes when available
|
||||||
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
|
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
|
||||||
return node::Buffer::Copy(isolate,
|
if (png->size() > 0) {
|
||||||
reinterpret_cast<const char*>(png->front()),
|
const char* data = reinterpret_cast<const char*>(png->front());
|
||||||
static_cast<size_t>(png->size())).ToLocalChecked();
|
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) {
|
||||||
|
float scale_factor = GetScaleFactorFromOptions(args);
|
||||||
|
|
||||||
|
if (scale_factor == 1.0f) {
|
||||||
|
// Use raw 1x PNG bytes when available
|
||||||
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
|
scoped_refptr<base::RefCountedMemory> png = image_.As1xPNGBytes();
|
||||||
std::string data_url;
|
if (png->size() > 0)
|
||||||
data_url.insert(data_url.end(), png->front(), png->front() + png->size());
|
return webui::GetPngDataUrl(png->front(), png->size());
|
||||||
base::Base64Encode(data_url, &data_url);
|
}
|
||||||
data_url.insert(0, "data:image/png;base64,");
|
|
||||||
return data_url;
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
BIN
spec/fixtures/assets/1x1.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 B |
Loading…
Reference in a new issue