Skip to content

Commit

Permalink
feat: support colorSpace: 'display-p3'
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Sep 30, 2021
1 parent a24d676 commit 4b64310
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 85 deletions.
4 changes: 1 addition & 3 deletions __test__/filter.spec.ts
Expand Up @@ -13,9 +13,7 @@ const test = ava as TestInterface<{
}>

const FIREFOX = readFileSync(join(__dirname, 'fixtures', 'firefox-logo.svg'))
const FIREFOX_IMAGE = new Image()
FIREFOX_IMAGE.width = 200
FIREFOX_IMAGE.height = 206.433
const FIREFOX_IMAGE = new Image(200, 206.433)
FIREFOX_IMAGE.src = FIREFOX

test.beforeEach((t) => {
Expand Down
17 changes: 14 additions & 3 deletions index.d.ts
Expand Up @@ -177,11 +177,15 @@ export class ImageData {
*/
readonly width: number

constructor(sw: number, sh: number)
constructor(sw: number, sh: number, attr?: { colorSpace?: ColorSpace })
constructor(imageData: ImageData, attr?: { colorSpace?: ColorSpace })
constructor(data: Uint8ClampedArray, sw: number, sh?: number)
}

export class Image {
constructor()
// attrs only affects SVG
constructor(width: number, height: number, attrs?: { colorSpace?: ColorSpace })
width: number
height: number
readonly naturalWidth: number
Expand Down Expand Up @@ -277,10 +281,17 @@ export interface SKRSContext2D
}
}

export type ColorSpace = 'srgb' | 'display-p3'

export interface ContextAttributes {
alpha?: boolean
colorSpace?: ColorSpace
}

export interface SvgCanvas {
width: number
height: number
getContext(contextType: '2d', contextAttributes?: { alpha: boolean }): SKRSContext2D
getContext(contextType: '2d', contextAttributes?: ContextAttributes): SKRSContext2D

getContent(): Buffer
}
Expand All @@ -290,7 +301,7 @@ export class Canvas {

width: number
height: number
getContext(contextType: '2d', contextAttributes?: { alpha: boolean }): SKRSContext2D
getContext(contextType: '2d', contextAttributes?: ContextAttributes): SKRSContext2D
encodeSync(format: 'webp' | 'jpeg', quality?: number): Buffer
encodeSync(format: 'png'): Buffer
encode(format: 'webp' | 'jpeg', quality?: number): Promise<Buffer>
Expand Down
19 changes: 8 additions & 11 deletions index.js
Expand Up @@ -163,20 +163,17 @@ function createCanvas(width, height, flag) {
const canvasElement = isSvgBackend ? new SVGCanvas(width, height) : new CanvasElement(width, height)

let ctx
canvasElement.getContext = function getContext(type, attr) {
canvasElement.getContext = function getContext(type, attr = {}) {
if (type !== '2d') {
throw new Error('Unsupported type')
}
const attrs = { alpha: true, colorSpace: 'srgb', ...attr }
ctx = ctx
? ctx
: isSvgBackend
? new CanvasRenderingContext2D(this.width, this.height, GlobalFontsSingleton, flag)
: new CanvasRenderingContext2D(this.width, this.height, GlobalFontsSingleton)
if (attr) {
createContext(ctx, this.width, this.height, attr)
} else {
createContext(ctx, this.width, this.height)
}
? new CanvasRenderingContext2D(this.width, this.height, GlobalFontsSingleton, attrs.colorSpace, flag)
: new CanvasRenderingContext2D(this.width, this.height, GlobalFontsSingleton, attrs.colorSpace)
createContext(ctx, this.width, this.height, attrs)

// napi can not define writable: true but enumerable: false property
Object.defineProperty(ctx, '_fillStyle', {
Expand All @@ -194,11 +191,11 @@ function createCanvas(width, height, flag) {
})

Object.defineProperty(ctx, 'createImageData', {
value: function createImageData(widthOrImage, height) {
value: function createImageData(widthOrImage, height, attrs = {}) {
if (widthOrImage instanceof ImageData) {
return new ImageData(widthOrImage.width, widthOrImage.height)
return new ImageData(widthOrImage.data, widthOrImage.width, widthOrImage.height)
}
return new ImageData(widthOrImage, height)
return new ImageData(widthOrImage, height, { colorSpace: 'srgb', ...attrs })
},
configurable: false,
enumerable: false,
Expand Down
38 changes: 21 additions & 17 deletions skia-c/skia_c.cpp
Expand Up @@ -12,6 +12,7 @@
#define MASK_FILTER_CAST reinterpret_cast<SkMaskFilter *>(c_mask_filter)
#define IMAGE_FILTER_CAST reinterpret_cast<SkImageFilter *>(c_image_filter)
#define TYPEFACE_CAST reinterpret_cast<SkTypeface *>(c_typeface)
#define COLOR_SPACE_CAST cs == 0 ? SkColorSpace::MakeSRGB() : SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3)

#define MAX_LAYOUT_WIDTH 100000
#define HANGING_AS_PERCENT_OF_ASCENT 80
Expand Down Expand Up @@ -57,12 +58,12 @@ extern "C"

// Surface

static SkSurface *skiac_surface_create(int width, int height, SkAlphaType alphaType)
static SkSurface *skiac_surface_create(int width, int height, SkAlphaType alphaType, uint8_t cs)
{
// Init() is idempotent, so can be called more than once with no adverse effect.
SkGraphics::Init();

auto info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, alphaType);
auto color_space = COLOR_SPACE_CAST;
auto info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, alphaType, color_space);
auto surface = SkSurface::MakeRaster(info);

if (surface)
Expand All @@ -76,7 +77,7 @@ extern "C"
}
}

void skiac_surface_create_svg(skiac_svg_surface *c_surface, int w, int h, int alphaType, uint32_t flag)
void skiac_surface_create_svg(skiac_svg_surface *c_surface, int w, int h, int alphaType, uint32_t flag, uint8_t cs)
{
auto w_stream = new SkDynamicMemoryWStream();

Expand All @@ -85,7 +86,7 @@ extern "C"
{
return;
}
auto surface = skiac_surface_create(w, h, (SkAlphaType)alphaType);
auto surface = skiac_surface_create(w, h, (SkAlphaType)alphaType, cs);
if (!surface)
{
return;
Expand All @@ -95,16 +96,16 @@ extern "C"
c_surface->canvas = reinterpret_cast<skiac_canvas *>(canvas.release());
}

skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height)
skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height, uint8_t cs)
{
return reinterpret_cast<skiac_surface *>(
skiac_surface_create(width, height, kPremul_SkAlphaType));
skiac_surface_create(width, height, kPremul_SkAlphaType, cs));
}

skiac_surface *skiac_surface_create_rgba(int width, int height)
skiac_surface *skiac_surface_create_rgba(int width, int height, uint8_t cs)
{
return reinterpret_cast<skiac_surface *>(
skiac_surface_create(width, height, kUnpremul_SkAlphaType));
skiac_surface_create(width, height, kUnpremul_SkAlphaType, cs));
}

bool skiac_surface_save(skiac_surface *c_surface, const char *path)
Expand Down Expand Up @@ -132,10 +133,10 @@ extern "C"

skiac_surface *skiac_surface_copy_rgba(
skiac_surface *c_surface,
uint32_t x, uint32_t y, uint32_t width, uint32_t height)
uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint8_t cs)
{
// x, y, width, height are source rectangle coordinates.
auto copy = skiac_surface_create((int)width, (int)height, kUnpremul_SkAlphaType);
auto copy = skiac_surface_create((int)width, (int)height, kUnpremul_SkAlphaType, cs);
if (!copy)
{
return nullptr;
Expand Down Expand Up @@ -179,9 +180,10 @@ extern "C"
}
}

bool skiac_surface_read_pixels_rect(skiac_surface *c_surface, uint8_t *data, int x, int y, int w, int h)
bool skiac_surface_read_pixels_rect(skiac_surface *c_surface, uint8_t *data, int x, int y, int w, int h, uint8_t cs)
{
auto image_info = SkImageInfo::Make(w, h, SkColorType::kRGBA_8888_SkColorType, SkAlphaType::kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
auto color_space = COLOR_SPACE_CAST;
auto image_info = SkImageInfo::Make(w, h, SkColorType::kRGBA_8888_SkColorType, SkAlphaType::kUnpremul_SkAlphaType, color_space);
auto result = SURFACE_CAST->readPixels(image_info, data, w * 4, x, y);
return result;
}
Expand Down Expand Up @@ -508,9 +510,10 @@ extern "C"
CANVAS_CAST->writePixels(info, pixels, row_bytes, x, y);
}

void skiac_canvas_write_pixels_dirty(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, size_t length, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height)
void skiac_canvas_write_pixels_dirty(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, size_t length, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height, uint8_t cs)
{
auto info = SkImageInfo::Make(width, height, SkColorType::kRGBA_8888_SkColorType, SkAlphaType::kUnpremul_SkAlphaType);
auto color_space = COLOR_SPACE_CAST;
auto info = SkImageInfo::Make(width, height, SkColorType::kRGBA_8888_SkColorType, SkAlphaType::kUnpremul_SkAlphaType, color_space);
auto data = SkData::MakeFromMalloc(pixels, length);
auto image = SkImage::MakeRasterData(info, data, row_bytes);
auto src_rect = SkRect::MakeXYWH(dirty_x, dirty_y, dirty_width, dirty_height);
Expand Down Expand Up @@ -1317,8 +1320,9 @@ extern "C"
bitmap_info->height = info.height();
}

void skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height, skiac_bitmap_info *bitmap_info)
void skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height, skiac_bitmap_info *bitmap_info, uint8_t cs)
{
auto color_space = COLOR_SPACE_CAST;
auto svg_stream = new SkMemoryStream(data, length, false);
auto svg_dom = SkSVGDOM::MakeFromStream(*svg_stream);
auto svg_root = svg_dom->getRoot();
Expand All @@ -1345,7 +1349,7 @@ extern "C"
image_w = width;
image_h = height;
}
auto imageinfo = SkImageInfo::Make(image_w, image_h, kRGBA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType);
auto imageinfo = SkImageInfo::Make(image_w, image_h, kRGBA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType, color_space);
auto bitmap = new SkBitmap();
bitmap->allocPixels(imageinfo);
auto sk_svg_canvas = new SkCanvas(*bitmap);
Expand Down
15 changes: 8 additions & 7 deletions skia-c/skia_c.hpp
Expand Up @@ -203,21 +203,22 @@ extern "C"
{

// Surface
skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height);
void skiac_surface_create_svg(skiac_svg_surface *c_surface, int width, int height, int alphaType, uint32_t flag);
skiac_surface *skiac_surface_create_rgba(int width, int height);
skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height, uint8_t cs);
void skiac_surface_create_svg(skiac_svg_surface *c_surface, int width, int height, int alphaType, uint32_t flag, uint8_t cs);
skiac_surface *skiac_surface_create_rgba(int width, int height, uint8_t cs);
void skiac_surface_destroy(skiac_surface *c_surface);
skiac_surface *skiac_surface_copy_rgba(
skiac_surface *c_surface,
uint32_t x,
uint32_t y,
uint32_t width,
uint32_t height);
uint32_t height,
uint8_t cs);
skiac_canvas *skiac_surface_get_canvas(skiac_surface *c_surface);
int skiac_surface_get_width(skiac_surface *c_surface);
int skiac_surface_get_height(skiac_surface *c_surface);
void skiac_surface_read_pixels(skiac_surface *c_surface, skiac_surface_data *data);
bool skiac_surface_read_pixels_rect(skiac_surface *c_surface, uint8_t *data, int x, int y, int w, int h);
bool skiac_surface_read_pixels_rect(skiac_surface *c_surface, uint8_t *data, int x, int y, int w, int h, uint8_t cs);
void skiac_surface_png_data(skiac_surface *c_surface, skiac_sk_data *data);
void skiac_surface_encode_data(skiac_surface *c_surface, skiac_sk_data *data, int format, int quality);
int skiac_surface_get_alpha_type(skiac_surface *c_surface);
Expand Down Expand Up @@ -285,7 +286,7 @@ extern "C"
void skiac_canvas_restore(skiac_canvas *c_canvas);
void skiac_canvas_reset(skiac_canvas *c_canvas);
void skiac_canvas_write_pixels(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, int x, int y);
void skiac_canvas_write_pixels_dirty(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, size_t length, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height);
void skiac_canvas_write_pixels_dirty(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, size_t length, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height, uint8_t cs);

// Paint
skiac_paint *skiac_paint_create();
Expand Down Expand Up @@ -425,7 +426,7 @@ extern "C"

// Bitmap
void skiac_bitmap_make_from_buffer(const uint8_t *ptr, size_t size, skiac_bitmap_info *bitmap_info);
void skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height, skiac_bitmap_info *bitmap_info);
void skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height, skiac_bitmap_info *bitmap_info, uint8_t cs);
skiac_bitmap *skiac_bitmap_make_from_image_data(uint8_t *ptr, size_t width, size_t height, size_t row_bytes, size_t size, int ct, int at);
size_t skiac_bitmap_get_width(skiac_bitmap *c_bitmap);
size_t skiac_bitmap_get_height(skiac_bitmap *c_bitmap);
Expand Down
52 changes: 41 additions & 11 deletions src/ctx.rs
Expand Up @@ -45,6 +45,7 @@ pub struct Context {
pub font_collection: Rc<FontCollection>,
pub width: u32,
pub height: u32,
pub color_space: ColorSpace,
pub stream: Option<SkWMemoryStream>,
pub filter: Option<ImageFilter>,
filters_string: String,
Expand Down Expand Up @@ -168,11 +169,17 @@ impl Context {
width: u32,
height: u32,
svg_export_flag: SvgExportFlag,
color_space: ColorSpace,
font_collection: &mut Rc<FontCollection>,
) -> Result<Self> {
let (surface, stream) =
Surface::new_svg(width, height, AlphaType::Unpremultiplied, svg_export_flag)
.ok_or_else(|| Error::from_reason("Create skia svg surface failed".to_owned()))?;
let (surface, stream) = Surface::new_svg(
width,
height,
AlphaType::Unpremultiplied,
svg_export_flag,
color_space,
)
.ok_or_else(|| Error::from_reason("Create skia svg surface failed".to_owned()))?;
Ok(Context {
surface,
alpha: true,
Expand All @@ -182,14 +189,20 @@ impl Context {
font_collection: font_collection.clone(),
width,
height,
color_space,
stream: Some(stream),
filter: None,
filters_string: "none".to_owned(),
})
}

pub fn new(width: u32, height: u32, font_collection: &mut Rc<FontCollection>) -> Result<Self> {
let surface = Surface::new_rgba(width, height)
pub fn new(
width: u32,
height: u32,
color_space: ColorSpace,
font_collection: &mut Rc<FontCollection>,
) -> Result<Self> {
let surface = Surface::new_rgba(width, height, color_space)
.ok_or_else(|| Error::from_reason("Create skia surface failed".to_owned()))?;
Ok(Context {
surface,
Expand All @@ -200,6 +213,7 @@ impl Context {
font_collection: font_collection.clone(),
width,
height,
color_space,
stream: None,
filter: None,
filters_string: "none".to_owned(),
Expand Down Expand Up @@ -719,23 +733,26 @@ impl Context {
}
}

#[js_function(4)]
#[js_function(5)]
fn context_2d_constructor(ctx: CallContext) -> Result<JsUndefined> {
let width = ctx.get::<JsNumber>(0)?.get_uint32()?;
let height = ctx.get::<JsNumber>(1)?.get_uint32()?;
let font_collection_js = ctx.get::<JsObject>(2)?;
let font_collection = ctx.env.unwrap::<Rc<FontCollection>>(&font_collection_js)?;
let color_space = ctx.get::<JsString>(3)?.into_utf8()?;
let color_space = ColorSpace::from_str(color_space.as_str()?)?;

let mut this = ctx.this_unchecked::<JsObject>();
let context_2d = if ctx.length == 3 {
Context::new(width, height, font_collection)?
let context_2d = if ctx.length == 4 {
Context::new(width, height, color_space, font_collection)?
} else {
// SVG Canvas
let flag = ctx.get::<JsNumber>(3)?.get_uint32()?;
let flag = ctx.get::<JsNumber>(4)?.get_uint32()?;
Context::new_svg(
width,
height,
SvgExportFlag::try_from(flag)?,
color_space,
font_collection,
)?
};
Expand Down Expand Up @@ -1362,17 +1379,29 @@ fn fill_text(ctx: CallContext) -> Result<JsUndefined> {
ctx.env.get_undefined()
}

#[js_function(4)]
#[js_function(5)]
fn get_image_data(ctx: CallContext) -> Result<JsTypedArray> {
let this = ctx.this_unchecked::<JsObject>();
let context_2d = ctx.env.unwrap::<Context>(&this)?;
let x = ctx.get::<JsNumber>(0)?.get_uint32()?;
let y = ctx.get::<JsNumber>(1)?.get_uint32()?;
let width = ctx.get::<JsNumber>(2)?.get_uint32()?;
let height = ctx.get::<JsNumber>(3)?.get_uint32()?;
let color_space = if ctx.length == 5 {
let image_settings = ctx.get::<JsObject>(4)?;
let cs = image_settings.get_named_property_unchecked::<JsUnknown>("colorSpace")?;
if cs.get_type()? == ValueType::String {
let color_space_js = unsafe { cs.cast::<JsString>() }.into_utf8()?;
ColorSpace::from_str(color_space_js.as_str()?)?
} else {
ColorSpace::default()
}
} else {
ColorSpace::default()
};
let pixels = context_2d
.surface
.read_pixels(x, y, width, height)
.read_pixels(x, y, width, height, color_space)
.ok_or_else(|| {
Error::new(
Status::GenericFailure,
Expand Down Expand Up @@ -1462,6 +1491,7 @@ fn put_image_data(ctx: CallContext) -> Result<JsUndefined> {
dirty_y,
dirty_width,
dirty_height,
image_data.color_space,
);
context_2d.surface.canvas.restore();
};
Expand Down

1 comment on commit 4b64310

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 4b64310 Previous: a24d676 Ratio
Draw house#skia-canvas 24 ops/sec (±1.39%) 23 ops/sec (±0.46%) 0.96
Draw house#node-canvas 19 ops/sec (±0.6%) 19 ops/sec (±0.32%) 1
Draw house#@napi-rs/skia 22 ops/sec (±0.87%) 21 ops/sec (±0.18%) 0.95
Draw gradient#skia-canvas 23 ops/sec (±1.65%) 21.5 ops/sec (±0.28%) 0.93
Draw gradient#node-canvas 18 ops/sec (±0.9%) 18.5 ops/sec (±0.28%) 1.03
Draw gradient#@napi-rs/skia 22 ops/sec (±0.49%) 20.6 ops/sec (±0.6%) 0.94

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.