Skip to content

Commit 878538f

Browse files
miniaknornagon
authored andcommitted
feat: add safer nativeImage.createFromBitmap(), which does not decode PNG/JPEG (electron#17337)
1 parent aa8b66a commit 878538f

4 files changed

Lines changed: 91 additions & 1 deletion

File tree

atom/common/api/atom_api_native_image.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,54 @@ mate::Handle<NativeImage> NativeImage::CreateFromPath(
504504
return handle;
505505
}
506506

507+
// static
508+
mate::Handle<NativeImage> NativeImage::CreateFromBitmap(
509+
mate::Arguments* args,
510+
v8::Local<v8::Value> buffer,
511+
const mate::Dictionary& options) {
512+
if (!node::Buffer::HasInstance(buffer)) {
513+
args->ThrowError("buffer must be a node Buffer");
514+
return mate::Handle<NativeImage>();
515+
}
516+
517+
unsigned int width = 0;
518+
unsigned int height = 0;
519+
double scale_factor = 1.;
520+
521+
if (!options.Get("width", &width)) {
522+
args->ThrowError("width is required");
523+
return mate::Handle<NativeImage>();
524+
}
525+
526+
if (!options.Get("height", &height)) {
527+
args->ThrowError("height is required");
528+
return mate::Handle<NativeImage>();
529+
}
530+
531+
auto info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
532+
auto size_bytes = info.computeMinByteSize();
533+
534+
if (size_bytes != node::Buffer::Length(buffer)) {
535+
args->ThrowError("invalid buffer size");
536+
return mate::Handle<NativeImage>();
537+
}
538+
539+
options.Get("scaleFactor", &scale_factor);
540+
541+
if (width == 0 || height == 0) {
542+
return CreateEmpty(args->isolate());
543+
}
544+
545+
SkBitmap bitmap;
546+
bitmap.allocN32Pixels(width, height, false);
547+
bitmap.setPixels(node::Buffer::Data(buffer));
548+
549+
gfx::ImageSkia image_skia;
550+
image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale_factor));
551+
552+
return Create(args->isolate(), gfx::Image(image_skia));
553+
}
554+
507555
// static
508556
mate::Handle<NativeImage> NativeImage::CreateFromBuffer(
509557
mate::Arguments* args,
@@ -618,6 +666,7 @@ void Initialize(v8::Local<v8::Object> exports,
618666
mate::Dictionary dict(context->GetIsolate(), exports);
619667
dict.SetMethod("createEmpty", &atom::api::NativeImage::CreateEmpty);
620668
dict.SetMethod("createFromPath", &atom::api::NativeImage::CreateFromPath);
669+
dict.SetMethod("createFromBitmap", &atom::api::NativeImage::CreateFromBitmap);
621670
dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer);
622671
dict.SetMethod("createFromDataURL",
623672
&atom::api::NativeImage::CreateFromDataURL);

atom/common/api/atom_api_native_image.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class NativeImage : public mate::Wrappable<NativeImage> {
5151
size_t length);
5252
static mate::Handle<NativeImage> CreateFromPath(v8::Isolate* isolate,
5353
const base::FilePath& path);
54+
static mate::Handle<NativeImage> CreateFromBitmap(
55+
mate::Arguments* args,
56+
v8::Local<v8::Value> buffer,
57+
const mate::Dictionary& options);
5458
static mate::Handle<NativeImage> CreateFromBuffer(
5559
mate::Arguments* args,
5660
v8::Local<v8::Value> buffer);

docs/api/native-image.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,19 @@ let image = nativeImage.createFromPath('/Users/somebody/images/icon.png')
137137
console.log(image)
138138
```
139139

140+
### `nativeImage.createFromBitmap(buffer, options)`
141+
142+
* `buffer` [Buffer][buffer]
143+
* `options` Object
144+
* `width` Integer
145+
* `height` Integer
146+
* `scaleFactor` Double (optional) - Defaults to 1.0.
147+
148+
Returns `NativeImage`
149+
150+
Creates a new `NativeImage` instance from `buffer` that contains the raw bitmap
151+
pixel data returned by `toBitmap()`. The specific format is platform-dependent.
152+
140153
### `nativeImage.createFromBuffer(buffer[, options])`
141154

142155
* `buffer` [Buffer][buffer]
@@ -147,7 +160,7 @@ console.log(image)
147160

148161
Returns `NativeImage`
149162

150-
Creates a new `NativeImage` instance from `buffer`.
163+
Creates a new `NativeImage` instance from `buffer`. Tries to decode as PNG or JPEG first.
151164

152165
### `nativeImage.createFromDataURL(dataURL)`
153166

spec/api-native-image-spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,30 @@ describe('nativeImage module', () => {
127127
})
128128
})
129129

130+
describe('createFromBitmap(buffer, options)', () => {
131+
it('returns an empty image when the buffer is empty', () => {
132+
expect(nativeImage.createFromBitmap(Buffer.from([]), { width: 0, height: 0 }).isEmpty())
133+
})
134+
135+
it('returns an image created from the given buffer', () => {
136+
const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
137+
138+
const imageB = nativeImage.createFromBitmap(imageA.toBitmap(), imageA.getSize())
139+
expect(imageB.getSize()).to.deep.equal({ width: 538, height: 190 })
140+
141+
const imageC = nativeImage.createFromBuffer(imageA.toBitmap(), { ...imageA.getSize(), scaleFactor: 2.0 })
142+
expect(imageC.getSize()).to.deep.equal({ width: 269, height: 95 })
143+
})
144+
145+
it('throws on invalid arguments', () => {
146+
expect(() => nativeImage.createFromBitmap(null, {})).to.throw('buffer must be a node Buffer')
147+
expect(() => nativeImage.createFromBitmap([12, 14, 124, 12], {})).to.throw('buffer must be a node Buffer')
148+
expect(() => nativeImage.createFromBitmap(Buffer.from([]), {})).to.throw('width is required')
149+
expect(() => nativeImage.createFromBitmap(Buffer.from([]), { width: 1 })).to.throw('height is required')
150+
expect(() => nativeImage.createFromBitmap(Buffer.from([]), { width: 1, height: 1 })).to.throw('invalid buffer size')
151+
})
152+
})
153+
130154
describe('createFromBuffer(buffer, options)', () => {
131155
it('returns an empty image when the buffer is empty', () => {
132156
expect(nativeImage.createFromBuffer(Buffer.from([])).isEmpty())

0 commit comments

Comments
 (0)