// Copyright (c) 2015 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/common/api/atom_api_native_image.h" #include #include #include #include #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" #include "base/threading/thread_restrictions.h" #include "native_mate/object_template_builder_deprecated.h" #include "net/base/data_url.h" #include "shell/common/asar/asar_util.h" #include "shell/common/native_mate_converters/file_path_converter.h" #include "shell/common/native_mate_converters/gfx_converter.h" #include "shell/common/native_mate_converters/gurl_converter.h" #include "shell/common/native_mate_converters/value_converter.h" #include "shell/common/node_includes.h" #include "shell/common/skia_util.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkImageInfo.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" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_operations.h" #include "ui/gfx/image/image_util.h" #if defined(OS_WIN) #include "base/win/scoped_gdi_object.h" #include "shell/common/asar/archive.h" #include "ui/gfx/icon_util.h" #endif namespace electron { namespace api { namespace { // 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; } base::FilePath NormalizePath(const base::FilePath& path) { if (!path.ReferencesParent()) { return path; } base::FilePath absolute_path = MakeAbsoluteFilePath(path); // MakeAbsoluteFilePath returns an empty path on failures so use original path if (absolute_path.empty()) { return path; } else { return absolute_path; } } #if defined(OS_MACOSX) bool IsTemplateFilename(const base::FilePath& path) { return (base::MatchPattern(path.value(), "*Template.*") || base::MatchPattern(path.value(), "*Template@*x.*")); } #endif #if defined(OS_WIN) base::win::ScopedHICON ReadICOFromPath(int size, const base::FilePath& path) { // If file is in asar archive, we extract it to a temp file so LoadImage can // load it. base::FilePath asar_path, relative_path; base::FilePath image_path(path); if (asar::GetAsarArchivePath(image_path, &asar_path, &relative_path)) { std::shared_ptr archive = asar::GetOrCreateAsarArchive(asar_path); if (archive) archive->CopyFileOut(relative_path, &image_path); } // Load the icon from file. return base::win::ScopedHICON( static_cast(LoadImage(NULL, image_path.value().c_str(), IMAGE_ICON, size, size, LR_LOADFROMFILE))); } #endif void Noop(char*, void*) {} } // namespace NativeImage::NativeImage(v8::Isolate* isolate, const gfx::Image& image) : image_(image) { Init(isolate); if (image_.HasRepresentation(gfx::Image::kImageRepSkia)) { isolate->AdjustAmountOfExternalAllocatedMemory( image_.ToImageSkia()->bitmap()->computeByteSize()); } } #if defined(OS_WIN) NativeImage::NativeImage(v8::Isolate* isolate, const base::FilePath& hicon_path) : hicon_path_(hicon_path) { // Use the 256x256 icon as fallback icon. gfx::ImageSkia image_skia; electron::util::ReadImageSkiaFromICO(&image_skia, GetHICON(256)); image_ = gfx::Image(image_skia); Init(isolate); if (image_.HasRepresentation(gfx::Image::kImageRepSkia)) { isolate->AdjustAmountOfExternalAllocatedMemory( image_.ToImageSkia()->bitmap()->computeByteSize()); } } #endif NativeImage::~NativeImage() { if (image_.HasRepresentation(gfx::Image::kImageRepSkia)) { isolate()->AdjustAmountOfExternalAllocatedMemory(-static_cast( image_.ToImageSkia()->bitmap()->computeByteSize())); } } #if defined(OS_WIN) HICON NativeImage::GetHICON(int size) { auto iter = hicons_.find(size); if (iter != hicons_.end()) return iter->second.get(); // First try loading the icon with specified size. if (!hicon_path_.empty()) { hicons_[size] = ReadICOFromPath(size, hicon_path_); return hicons_[size].get(); } // Then convert the image to ICO. if (image_.IsEmpty()) return NULL; hicons_[size] = IconUtil::CreateHICONFromSkBitmap(image_.AsBitmap()); return hicons_[size].get(); } #endif 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).GetBitmap(); std::vector encoded; gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &encoded); 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(mate::Arguments* args) { float scale_factor = GetScaleFactorFromOptions(args); const SkBitmap bitmap = image_.AsImageSkia().GetRepresentation(scale_factor).GetBitmap(); 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.computeByteSize()) .ToLocalChecked(); } v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { std::vector output; gfx::JPEG1xEncodedDataFromImage(image_, quality, &output); if (output.empty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); return node::Buffer::Copy(isolate, reinterpret_cast(&output.front()), output.size()) .ToLocalChecked(); } 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).GetBitmap()); } v8::Local NativeImage::GetBitmap(mate::Arguments* args) { float scale_factor = GetScaleFactorFromOptions(args); const SkBitmap bitmap = image_.AsImageSkia().GetRepresentation(scale_factor).GetBitmap(); 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.computeByteSize(), &Noop, nullptr) .ToLocalChecked(); } v8::Local NativeImage::GetNativeHandle(v8::Isolate* isolate, mate::Arguments* args) { #if defined(OS_MACOSX) if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); NSImage* ptr = image_.AsNSImage(); return node::Buffer::Copy(isolate, reinterpret_cast(ptr), sizeof(void*)) .ToLocalChecked(); #else args->ThrowError("Not implemented"); return v8::Undefined(isolate); #endif } bool NativeImage::IsEmpty() { return image_.IsEmpty(); } gfx::Size NativeImage::GetSize() { return image_.Size(); } float NativeImage::GetAspectRatio() { gfx::Size size = GetSize(); if (size.IsEmpty()) return 1.f; else return static_cast(size.width()) / static_cast(size.height()); } mate::Handle NativeImage::Resize( v8::Isolate* isolate, const base::DictionaryValue& options) { gfx::Size size = GetSize(); int width = size.width(); int height = size.height(); bool width_set = options.GetInteger("width", &width); bool height_set = options.GetInteger("height", &height); size.SetSize(width, height); if (width_set && !height_set) { // Scale height to preserve original aspect ratio size.set_height(width); size = gfx::ScaleToRoundedSize(size, 1.f, 1.f / GetAspectRatio()); } else if (height_set && !width_set) { // Scale width to preserve original aspect ratio size.set_width(height); size = gfx::ScaleToRoundedSize(size, GetAspectRatio(), 1.f); } skia::ImageOperations::ResizeMethod method = skia::ImageOperations::ResizeMethod::RESIZE_BEST; std::string quality; options.GetString("quality", &quality); if (quality == "good") method = skia::ImageOperations::ResizeMethod::RESIZE_GOOD; else if (quality == "better") method = skia::ImageOperations::ResizeMethod::RESIZE_BETTER; gfx::ImageSkia resized = gfx::ImageSkiaOperations::CreateResizedImage( image_.AsImageSkia(), method, size); return mate::CreateHandle(isolate, new NativeImage(isolate, gfx::Image(resized))); } mate::Handle NativeImage::Crop(v8::Isolate* isolate, const gfx::Rect& rect) { gfx::ImageSkia cropped = gfx::ImageSkiaOperations::ExtractSubset(image_.AsImageSkia(), rect); return mate::CreateHandle(isolate, new NativeImage(isolate, gfx::Image(cropped))); } void NativeImage::AddRepresentation(const mate::Dictionary& options) { int width = 0; int height = 0; float scale_factor = 1.0f; options.Get("width", &width); options.Get("height", &height); options.Get("scaleFactor", &scale_factor); bool skia_rep_added = false; gfx::ImageSkia image_skia = image_.AsImageSkia(); v8::Local buffer; GURL url; if (options.Get("buffer", &buffer) && node::Buffer::HasInstance(buffer)) { auto* data = reinterpret_cast(node::Buffer::Data(buffer)); auto size = node::Buffer::Length(buffer); skia_rep_added = electron::util::AddImageSkiaRepFromBuffer( &image_skia, data, size, width, height, scale_factor); } else if (options.Get("dataURL", &url)) { std::string mime_type, charset, data; if (net::DataURL::Parse(url, &mime_type, &charset, &data)) { auto* data_ptr = reinterpret_cast(data.c_str()); if (mime_type == "image/png") { skia_rep_added = electron::util::AddImageSkiaRepFromPNG( &image_skia, data_ptr, data.size(), scale_factor); } else if (mime_type == "image/jpeg") { skia_rep_added = electron::util::AddImageSkiaRepFromJPEG( &image_skia, data_ptr, data.size(), scale_factor); } } } // Re-initialize image when first representation is added to an empty image if (skia_rep_added && IsEmpty()) { gfx::Image image(image_skia); image_ = std::move(image); } } #if !defined(OS_MACOSX) void NativeImage::SetTemplateImage(bool setAsTemplate) {} bool NativeImage::IsTemplateImage() { return false; } #endif // static mate::Handle NativeImage::CreateEmpty(v8::Isolate* isolate) { return mate::CreateHandle(isolate, new NativeImage(isolate, gfx::Image())); } // static mate::Handle NativeImage::Create(v8::Isolate* isolate, const gfx::Image& image) { return mate::CreateHandle(isolate, new NativeImage(isolate, image)); } // static mate::Handle NativeImage::CreateFromPNG(v8::Isolate* isolate, const char* buffer, size_t length) { gfx::Image image = gfx::Image::CreateFrom1xPNGBytes( reinterpret_cast(buffer), length); return Create(isolate, image); } // static mate::Handle NativeImage::CreateFromJPEG(v8::Isolate* isolate, const char* buffer, size_t length) { gfx::Image image = gfx::ImageFrom1xJPEGEncodedData( reinterpret_cast(buffer), length); return Create(isolate, image); } // static mate::Handle NativeImage::CreateFromPath( v8::Isolate* isolate, const base::FilePath& path) { base::FilePath image_path = NormalizePath(path); #if defined(OS_WIN) if (image_path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { return mate::CreateHandle(isolate, new NativeImage(isolate, image_path)); } #endif gfx::ImageSkia image_skia; electron::util::PopulateImageSkiaRepsFromPath(&image_skia, image_path); gfx::Image image(image_skia); mate::Handle handle = Create(isolate, image); #if defined(OS_MACOSX) if (IsTemplateFilename(image_path)) handle->SetTemplateImage(true); #endif return handle; } // static mate::Handle NativeImage::CreateFromBitmap( mate::Arguments* args, v8::Local buffer, const mate::Dictionary& options) { if (!node::Buffer::HasInstance(buffer)) { args->ThrowError("buffer must be a node Buffer"); return mate::Handle(); } unsigned int width = 0; unsigned int height = 0; double scale_factor = 1.; if (!options.Get("width", &width)) { args->ThrowError("width is required"); return mate::Handle(); } if (!options.Get("height", &height)) { args->ThrowError("height is required"); return mate::Handle(); } auto info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); auto size_bytes = info.computeMinByteSize(); if (size_bytes != node::Buffer::Length(buffer)) { args->ThrowError("invalid buffer size"); return mate::Handle(); } options.Get("scaleFactor", &scale_factor); if (width == 0 || height == 0) { return CreateEmpty(args->isolate()); } SkBitmap bitmap; bitmap.allocN32Pixels(width, height, false); bitmap.writePixels({info, node::Buffer::Data(buffer), bitmap.rowBytes()}); gfx::ImageSkia image_skia; image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale_factor)); return Create(args->isolate(), gfx::Image(image_skia)); } // static mate::Handle NativeImage::CreateFromBuffer( mate::Arguments* args, v8::Local buffer) { if (!node::Buffer::HasInstance(buffer)) { args->ThrowError("buffer must be a node Buffer"); return mate::Handle(); } int width = 0; int height = 0; double scale_factor = 1.; mate::Dictionary options; if (args->GetNext(&options)) { options.Get("width", &width); options.Get("height", &height); options.Get("scaleFactor", &scale_factor); } gfx::ImageSkia image_skia; electron::util::AddImageSkiaRepFromBuffer( &image_skia, reinterpret_cast(node::Buffer::Data(buffer)), node::Buffer::Length(buffer), width, height, scale_factor); return Create(args->isolate(), gfx::Image(image_skia)); } // static mate::Handle NativeImage::CreateFromDataURL(v8::Isolate* isolate, const GURL& url) { std::string mime_type, charset, data; if (net::DataURL::Parse(url, &mime_type, &charset, &data)) { if (mime_type == "image/png") return CreateFromPNG(isolate, data.c_str(), data.size()); else if (mime_type == "image/jpeg") return CreateFromJPEG(isolate, data.c_str(), data.size()); } return CreateEmpty(isolate); } #if !defined(OS_MACOSX) mate::Handle NativeImage::CreateFromNamedImage( mate::Arguments* args, const std::string& name) { return CreateEmpty(args->isolate()); } #endif // static void NativeImage::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "NativeImage")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetMethod("toPNG", &NativeImage::ToPNG) .SetMethod("toJPEG", &NativeImage::ToJPEG) .SetMethod("toBitmap", &NativeImage::ToBitmap) .SetMethod("getBitmap", &NativeImage::GetBitmap) .SetMethod("getNativeHandle", &NativeImage::GetNativeHandle) .SetMethod("toDataURL", &NativeImage::ToDataURL) .SetMethod("isEmpty", &NativeImage::IsEmpty) .SetMethod("getSize", &NativeImage::GetSize) .SetMethod("_setTemplateImage", &NativeImage::SetTemplateImage) .SetMethod("_isTemplateImage", &NativeImage::IsTemplateImage) .SetProperty("isMacTemplateImage", &NativeImage::IsTemplateImage, &NativeImage::SetTemplateImage) .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) .SetMethod("addRepresentation", &NativeImage::AddRepresentation); } } // namespace api } // namespace electron namespace mate { v8::Local Converter>::ToV8( v8::Isolate* isolate, const mate::Handle& val) { return val.ToV8(); } bool Converter>::FromV8( v8::Isolate* isolate, v8::Local val, mate::Handle* out) { // Try converting from file path. base::FilePath path; if (ConvertFromV8(isolate, val, &path)) { *out = electron::api::NativeImage::CreateFromPath(isolate, path); // Should throw when failed to initialize from path. return !(*out)->image().IsEmpty(); } WrappableBase* wrapper = static_cast(internal::FromV8Impl(isolate, val)); if (!wrapper) return false; *out = CreateHandle(isolate, static_cast(wrapper)); return true; } } // namespace mate namespace { using electron::api::NativeImage; void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("NativeImage", NativeImage::GetConstructor(isolate) ->GetFunction(context) .ToLocalChecked()); mate::Dictionary native_image = mate::Dictionary::CreateEmpty(isolate); dict.Set("nativeImage", native_image); native_image.SetMethod("createEmpty", &NativeImage::CreateEmpty); native_image.SetMethod("createFromPath", &NativeImage::CreateFromPath); native_image.SetMethod("createFromBitmap", &NativeImage::CreateFromBitmap); native_image.SetMethod("createFromBuffer", &NativeImage::CreateFromBuffer); native_image.SetMethod("createFromDataURL", &NativeImage::CreateFromDataURL); native_image.SetMethod("createFromNamedImage", &NativeImage::CreateFromNamedImage); } } // namespace NODE_LINKED_MODULE_CONTEXT_AWARE(atom_common_native_image, Initialize)