From b757720cc52c5367e40908ad98b7200f24b53ea7 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 01:48:25 +0300 Subject: [PATCH 01/20] Add support of WEBP format conversion, add lossy flag for WEBP format, add image quality support for PNG format --- package/cpp/api/JsiSkImage.h | 39 ++++++++++++++++++++++----- package/src/skia/types/Image/Image.ts | 3 ++- package/src/skia/web/JsiSkImage.ts | 10 ++++--- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 90581e9eb7..c198d19eb5 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -17,6 +17,7 @@ #include "include/codec/SkEncodedImageFormat.h" #include "include/encode/SkJpegEncoder.h" #include "include/encode/SkPngEncoder.h" +#include "include/encode/SkWebpEncoder.h" #pragma clang diagnostic pop @@ -27,6 +28,10 @@ namespace RNSkia { namespace jsi = facebook::jsi; class JsiSkImage : public JsiSkWrappingSkPtrHostObject { +private: + double lerp(double a, double b, double t) { + return (a * (1.0 - t)) + (b * t); + } public: // TODO-API: Properties? JSI_HOST_FUNCTION(width) { return static_cast(getObject()->width()); } @@ -76,14 +81,34 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { image = image->makeNonTextureImage(); } sk_sp data; - if (format == SkEncodedImageFormat::kJPEG) { - SkJpegEncoder::Options options; - options.fQuality = quality; - data = SkJpegEncoder::Encode(nullptr, image.get(), options); - } else { - SkPngEncoder::Options options; - data = SkPngEncoder::Encode(nullptr, image.get(), options); + + switch (format) { + case SkEncodedImageFormat::kJPEG: { + SkJpegEncoder::Options options; + options.fQuality = quality; + data = SkJpegEncoder::Encode(nullptr, image.get(), options); + break; + }; + case SkEncodedImageFormat::kWEBP: { + const bool lossy = count == 3 ? arguments[2].asBool() : true; + + SkWebpEncoder::Options options; + options.fQuality = quality; + options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; + data = SkWebpEncoder::Encode(nullptr, image.get(), options); + break; + }; + default: { + const double t = quality / 100.0; + const int level = (int)std::round(lerp(9.0, 0.0, t)); // Must be in [0, 9] where 9 corresponds to maximal compression. + + SkPngEncoder::Options options; + options.fZLibLevel = level; + data = SkPngEncoder::Encode(nullptr, image.get(), options); + break; + }; } + return data; } diff --git a/package/src/skia/types/Image/Image.ts b/package/src/skia/types/Image/Image.ts index a6a1ef5321..e3009359dc 100644 --- a/package/src/skia/types/Image/Image.ts +++ b/package/src/skia/types/Image/Image.ts @@ -89,10 +89,11 @@ export interface SkImage extends SkJSIInstance<"Image"> { @param fmt - PNG is the default value. @param quality - a value from 0 to 100; 100 is the least lossy. May be ignored. + @param lossy - WEBP lossy compression flag, default value is true. May be ignored. @return base64 encoded string of data */ - encodeToBase64(fmt?: ImageFormat, quality?: number): string; + encodeToBase64(fmt?: ImageFormat, quality?: number, lossy?: boolean): string; /** * Returns raster image or lazy image. Copies SkImage backed by GPU texture diff --git a/package/src/skia/web/JsiSkImage.ts b/package/src/skia/web/JsiSkImage.ts index dcb7403d66..474187473c 100644 --- a/package/src/skia/web/JsiSkImage.ts +++ b/package/src/skia/web/JsiSkImage.ts @@ -90,9 +90,11 @@ export class JsiSkImage extends HostObject implements SkImage { ); } - encodeToBytes(fmt?: ImageFormat, quality?: number) { + encodeToBytes(fmt?: ImageFormat, quality?: number, lossy?: boolean) { let result: Uint8Array | null; - if (fmt && quality) { + if (fmt && quality && lossy) { + result = this.ref.encodeToBytes(ckEnum(fmt), quality, lossy); + } else if (fmt && quality) { result = this.ref.encodeToBytes(ckEnum(fmt), quality); } else if (fmt) { result = this.ref.encodeToBytes(ckEnum(fmt)); @@ -105,8 +107,8 @@ export class JsiSkImage extends HostObject implements SkImage { return result; } - encodeToBase64(fmt?: ImageFormat, quality?: number) { - const bytes = this.encodeToBytes(fmt, quality); + encodeToBase64(fmt?: ImageFormat, quality?: number, lossy?: boolean) { + const bytes = this.encodeToBytes(fmt, quality, lossy); return toBase64String(bytes); } From e9fde2c75ece2feee0e80b9859369d0e9f995cd4 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 02:14:27 +0300 Subject: [PATCH 02/20] Fix cpplint errors --- package/cpp/api/JsiSkImage.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index c198d19eb5..52529c50b4 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -96,11 +96,11 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { options.fQuality = quality; options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; data = SkWebpEncoder::Encode(nullptr, image.get(), options); - break; + break; }; default: { const double t = quality / 100.0; - const int level = (int)std::round(lerp(9.0, 0.0, t)); // Must be in [0, 9] where 9 corresponds to maximal compression. + const int level = static_cast(std::round(lerp(9.0, 0.0, t))); // Must be in [0, 9] where 9 corresponds to maximal compression. SkPngEncoder::Options options; options.fZLibLevel = level; From 94f85ebf381d2c5e2bce5a916d4b6a176d4c5f95 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 02:22:46 +0300 Subject: [PATCH 03/20] Replace switch/case with if/else to avoid linter errors --- package/cpp/api/JsiSkImage.h | 43 +++++++++++++++--------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 52529c50b4..18cf742de9 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -82,31 +82,24 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { } sk_sp data; - switch (format) { - case SkEncodedImageFormat::kJPEG: { - SkJpegEncoder::Options options; - options.fQuality = quality; - data = SkJpegEncoder::Encode(nullptr, image.get(), options); - break; - }; - case SkEncodedImageFormat::kWEBP: { - const bool lossy = count == 3 ? arguments[2].asBool() : true; - - SkWebpEncoder::Options options; - options.fQuality = quality; - options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; - data = SkWebpEncoder::Encode(nullptr, image.get(), options); - break; - }; - default: { - const double t = quality / 100.0; - const int level = static_cast(std::round(lerp(9.0, 0.0, t))); // Must be in [0, 9] where 9 corresponds to maximal compression. - - SkPngEncoder::Options options; - options.fZLibLevel = level; - data = SkPngEncoder::Encode(nullptr, image.get(), options); - break; - }; + if (format == SkEncodedImageFormat::kJPEG) { + SkJpegEncoder::Options options; + options.fQuality = quality; + data = SkJpegEncoder::Encode(nullptr, image.get(), options); + } else if (format == SkEncodedImageFormat::kWEBP) { + const bool lossy = count == 3 ? arguments[2].asBool() : true; + + SkWebpEncoder::Options options; + options.fQuality = quality; + options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; + data = SkWebpEncoder::Encode(nullptr, image.get(), options); + } else { + const double t = quality / 100.0; + const int level = static_cast(std::round(lerp(9.0, 0.0, t))); + + SkPngEncoder::Options options; + options.fZLibLevel = level; // Must be in [0, 9] where 9 corresponds to maximal compression. + data = SkPngEncoder::Encode(nullptr, image.get(), options); } return data; From 58ac14fc458d8b379d05dd67eecd7fa84b0baca9 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 02:32:46 +0300 Subject: [PATCH 04/20] Fix clang-format errors --- package/cpp/api/JsiSkImage.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 18cf742de9..8270bf420d 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -32,6 +32,7 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { double lerp(double a, double b, double t) { return (a * (1.0 - t)) + (b * t); } + public: // TODO-API: Properties? JSI_HOST_FUNCTION(width) { return static_cast(getObject()->width()); } @@ -91,14 +92,17 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { SkWebpEncoder::Options options; options.fQuality = quality; - options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; + options.fCompression = + lossy ? SkWebpEncoder::Compression::kLossy + : SkWebpEncoder::Compression::kLossless; data = SkWebpEncoder::Encode(nullptr, image.get(), options); } else { const double t = quality / 100.0; const int level = static_cast(std::round(lerp(9.0, 0.0, t))); SkPngEncoder::Options options; - options.fZLibLevel = level; // Must be in [0, 9] where 9 corresponds to maximal compression. + // must be in [0, 9] where 9 corresponds to maximal compression. + options.fZLibLevel = level; data = SkPngEncoder::Encode(nullptr, image.get(), options); } From 36870697b15b7ecff2957112ba27795a0435c529 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 02:48:09 +0300 Subject: [PATCH 05/20] Add lossy argument to SkImage.encodeToBytes method --- package/src/skia/types/Image/Image.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package/src/skia/types/Image/Image.ts b/package/src/skia/types/Image/Image.ts index e3009359dc..0b6f5b9aad 100644 --- a/package/src/skia/types/Image/Image.ts +++ b/package/src/skia/types/Image/Image.ts @@ -74,10 +74,12 @@ export interface SkImage extends SkJSIInstance<"Image"> { @param fmt - PNG is the default value. @param quality - a value from 0 to 100; 100 is the least lossy. May be ignored. + @param lossy - WEBP lossy compression flag, default value is true. May be ignored. + @return Uint8Array with data */ - encodeToBytes(fmt?: ImageFormat, quality?: number): Uint8Array; + encodeToBytes(fmt?: ImageFormat, quality?: number, lossy?: boolean): Uint8Array; /** Encodes Image pixels, returning result as a base64 encoded string. Returns existing encoded data if present; otherwise, SkImage is encoded with From cf14fc4997037e08e8f858ee19378525a94f6238 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 02:53:49 +0300 Subject: [PATCH 06/20] Fix formatting with clang-format-common --- package/cpp/api/JsiSkImage.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 8270bf420d..ac1665c94c 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -92,9 +92,8 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { SkWebpEncoder::Options options; options.fQuality = quality; - options.fCompression = - lossy ? SkWebpEncoder::Compression::kLossy - : SkWebpEncoder::Compression::kLossless; + options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy + : SkWebpEncoder::Compression::kLossless; data = SkWebpEncoder::Encode(nullptr, image.get(), options); } else { const double t = quality / 100.0; From 4328a362249c012182e948d2edffacb5c317d189 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 03:01:40 +0300 Subject: [PATCH 07/20] Fix eslint errors --- package/src/skia/types/Image/Image.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package/src/skia/types/Image/Image.ts b/package/src/skia/types/Image/Image.ts index 0b6f5b9aad..117f1b1c47 100644 --- a/package/src/skia/types/Image/Image.ts +++ b/package/src/skia/types/Image/Image.ts @@ -79,7 +79,11 @@ export interface SkImage extends SkJSIInstance<"Image"> { @return Uint8Array with data */ - encodeToBytes(fmt?: ImageFormat, quality?: number, lossy?: boolean): Uint8Array; + encodeToBytes( + fmt?: ImageFormat, + quality?: number, + lossy?: boolean + ): Uint8Array; /** Encodes Image pixels, returning result as a base64 encoded string. Returns existing encoded data if present; otherwise, SkImage is encoded with From a77811e77cf367606abec46e19b62e25ab766ddb Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 03:12:35 +0300 Subject: [PATCH 08/20] Remove webp lossy parameter from web version --- package/src/skia/web/JsiSkImage.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/package/src/skia/web/JsiSkImage.ts b/package/src/skia/web/JsiSkImage.ts index 474187473c..dcb7403d66 100644 --- a/package/src/skia/web/JsiSkImage.ts +++ b/package/src/skia/web/JsiSkImage.ts @@ -90,11 +90,9 @@ export class JsiSkImage extends HostObject implements SkImage { ); } - encodeToBytes(fmt?: ImageFormat, quality?: number, lossy?: boolean) { + encodeToBytes(fmt?: ImageFormat, quality?: number) { let result: Uint8Array | null; - if (fmt && quality && lossy) { - result = this.ref.encodeToBytes(ckEnum(fmt), quality, lossy); - } else if (fmt && quality) { + if (fmt && quality) { result = this.ref.encodeToBytes(ckEnum(fmt), quality); } else if (fmt) { result = this.ref.encodeToBytes(ckEnum(fmt)); @@ -107,8 +105,8 @@ export class JsiSkImage extends HostObject implements SkImage { return result; } - encodeToBase64(fmt?: ImageFormat, quality?: number, lossy?: boolean) { - const bytes = this.encodeToBytes(fmt, quality, lossy); + encodeToBase64(fmt?: ImageFormat, quality?: number) { + const bytes = this.encodeToBytes(fmt, quality); return toBase64String(bytes); } From 98cfdc06b2db412310435ed272ca00aed7717b0b Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 05:08:14 +0300 Subject: [PATCH 09/20] Fix quality value assignment --- package/cpp/api/JsiSkImage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index ac1665c94c..1c0f6dda10 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -76,7 +76,7 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { count >= 1 ? static_cast(arguments[0].asNumber()) : SkEncodedImageFormat::kPNG; - auto quality = count == 2 ? arguments[1].asNumber() : 100.0; + auto quality = count >= 2 ? arguments[1].asNumber() : 100.0; auto image = getObject(); if (image->isTextureBacked()) { image = image->makeNonTextureImage(); From ddeff16da8095287853d48acf6c8a5787bd1bade Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 06:24:27 +0300 Subject: [PATCH 10/20] Add type checking for optional arguments --- package/cpp/api/JsiSkImage.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 1c0f6dda10..cc23e6d740 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -76,7 +76,9 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { count >= 1 ? static_cast(arguments[0].asNumber()) : SkEncodedImageFormat::kPNG; - auto quality = count >= 2 ? arguments[1].asNumber() : 100.0; + auto quality = (count >= 2 && arguments[1].isNumber()) + ? arguments[1].asNumber() + : 100.0; auto image = getObject(); if (image->isTextureBacked()) { image = image->makeNonTextureImage(); @@ -88,7 +90,8 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { options.fQuality = quality; data = SkJpegEncoder::Encode(nullptr, image.get(), options); } else if (format == SkEncodedImageFormat::kWEBP) { - const bool lossy = count == 3 ? arguments[2].asBool() : true; + const bool lossy = + (count >= 3 && arguments[2].isBool()) ? arguments[2].asBool() : true; SkWebpEncoder::Options options; options.fQuality = quality; From c3c3cc5e98595869b10eb3384263c63bb8887a73 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 6 Nov 2023 23:59:12 +0300 Subject: [PATCH 11/20] Add e2e tests --- .../__tests__/e2e/ImageEncoding.spec.tsx | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx new file mode 100644 index 0000000000..5ce92eb701 --- /dev/null +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -0,0 +1,292 @@ +import { surface } from "../setup"; + +const MAGIC_BYTES = { + JPEG: "/9j/4", + PNG: "iVBORw0KGgo", + WEBP: "UklGR", +}; + +const IMAGE_FORMAT = { + JPEG: 3, // ImageFormat.JPEG, + PNG: 4, // ImageFormat.PNG, + WEBP: 6, // ImageFormat.WEBP, +} + +const IMAGE_INFO_BASE = { + alphaType: 1, // AlphaType.Opaque, + colorType: 4, // ColorType.RGBA_8888, +} + +describe("Image Encoding", () => { + it("SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1; + const height = 1; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! + .encodeToBase64(ctx.format) + .slice(0, ctx.cutIndex); + }, { cutIndex: MAGIC_BYTES.WEBP.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); + + expect(result).toEqual(MAGIC_BYTES.WEBP); + }); + + + it("SkImage.encodeToBase64: check PNG format by magic bytes sequence iVBORw0KGgo...", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1; + const height = 1; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! + .encodeToBase64(ctx.format) + .slice(0, ctx.cutIndex); + }, { cutIndex: MAGIC_BYTES.PNG.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG }); + + expect(result).toEqual(MAGIC_BYTES.PNG); + }); + + it("SkImage.encodeToBase64: check JPEG format by magic bytes sequence /9j/4...", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1; + const height = 1; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! + .encodeToBase64(ctx.format) + .slice(0, ctx.cutIndex); + }, { cutIndex: MAGIC_BYTES.JPEG.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.JPEG }); + + expect(result).toEqual(MAGIC_BYTES.JPEG); + }); + + + it("SkImage.encodeToBase64: JPEG checking of the \"quality\" argument work", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + let i = 0; + for (let x = 0; x < 256; x++) { + for (let y = 0; y < 256; y++) { + bytes[i++] = (x * y) % 255; + } + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const minQuality = image.encodeToBase64(ctx.format, 0).length; + const midQuality = image.encodeToBase64(ctx.format, 50).length; + const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100).length; + + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.JPEG }); + + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); + + // this test can be failed on CanvasKit + it("SkImage.encodeToBase64: PNG checking of the \"quality\" argument work", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < 256; x++) { + for (let y = 0; y < 256; y++) { + bytes[i++] = (x * y) % 255; + } + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const minQuality = image.encodeToBase64(ctx.format, 0).length; + const midQuality = image.encodeToBase64(ctx.format, 50).length; + const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100).length; + + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG }); + + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); + + // this test can be failed on CanvasKit + it("SkImage.encodeToBase64: WEBP checking of the \"quality\" argument work with default lossy - true", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < 256; x++) { + for (let y = 0; y < 256; y++) { + bytes[i++] = (x * y) % 255; + } + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const minQuality = image.encodeToBase64(ctx.format, 0, true).length; + const midQuality = image.encodeToBase64(ctx.format, 50, true).length; + const defaultQuality = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; + + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); + + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); + + // this test can be failed on CanvasKit + it("SkImage.encodeToBase64: WEBP checking of the \"quality\" argument work with lossless", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < 256; x++) { + for (let y = 0; y < 256; y++) { + bytes[i++] = (x * y) % 255; + } + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const minQuality = image.encodeToBase64(ctx.format, 0, false).length; + const midQuality = image.encodeToBase64(ctx.format, 50, false).length; + const defaultQuality = image.encodeToBase64(ctx.format, undefined, false).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100, false).length; + + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); + + expect(result.minQuality).not.toEqual(result.midQuality); + expect(result.minQuality).not.toEqual(result.defaultQuality); + expect(result.minQuality).not.toEqual(result.maxQuality); + expect(result.midQuality).not.toEqual(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); + + // this test can be failed on CanvasKit + it("SkImage.encodeToBase64: WEBP checking of the \"lossy\" argument work", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < 256; x++) { + for (let y = 0; y < 256; y++) { + bytes[i++] = (x * y) % 255; + } + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. + const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. + const lossless = image.encodeToBase64(ctx.format, undefined, false).length; // default quality: 100. + + return { + defaultLossy, + lossy, + lossless, + }; + }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); + + expect(result.lossy).toEqual(result.defaultLossy); + expect(result.lossy).not.toEqual(result.lossless); + expect(result.defaultLossy).not.toEqual(result.lossless); + }); +}); From 0ecfc205ef21a877e4df4b720106fc620a82c873 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Tue, 7 Nov 2023 00:04:18 +0300 Subject: [PATCH 12/20] Fix eslint errors --- .../__tests__/e2e/ImageEncoding.spec.tsx | 130 +++++++++++++----- 1 file changed, 94 insertions(+), 36 deletions(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 5ce92eb701..1aad989520 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -10,12 +10,12 @@ const IMAGE_FORMAT = { JPEG: 3, // ImageFormat.JPEG, PNG: 4, // ImageFormat.PNG, WEBP: 6, // ImageFormat.WEBP, -} +}; const IMAGE_INFO_BASE = { alphaType: 1, // AlphaType.Opaque, colorType: 4, // ColorType.RGBA_8888, -} +}; describe("Image Encoding", () => { it("SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", async () => { @@ -32,16 +32,21 @@ describe("Image Encoding", () => { width, height, }; - + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! .encodeToBase64(ctx.format) .slice(0, ctx.cutIndex); - }, { cutIndex: MAGIC_BYTES.WEBP.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); - + }, + { + cutIndex: MAGIC_BYTES.WEBP.length, + imageInfoBase: IMAGE_INFO_BASE, + format: IMAGE_FORMAT.WEBP, + } + ); + expect(result).toEqual(MAGIC_BYTES.WEBP); }); - it("SkImage.encodeToBase64: check PNG format by magic bytes sequence iVBORw0KGgo...", async () => { const result = await surface.eval( (Skia, ctx) => { @@ -56,12 +61,18 @@ describe("Image Encoding", () => { width, height, }; - + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! .encodeToBase64(ctx.format) .slice(0, ctx.cutIndex); - }, { cutIndex: MAGIC_BYTES.PNG.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG }); - + }, + { + cutIndex: MAGIC_BYTES.PNG.length, + imageInfoBase: IMAGE_INFO_BASE, + format: IMAGE_FORMAT.PNG, + } + ); + expect(result).toEqual(MAGIC_BYTES.PNG); }); @@ -79,17 +90,22 @@ describe("Image Encoding", () => { width, height, }; - + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! .encodeToBase64(ctx.format) .slice(0, ctx.cutIndex); - }, { cutIndex: MAGIC_BYTES.JPEG.length, imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.JPEG }); - + }, + { + cutIndex: MAGIC_BYTES.JPEG.length, + imageInfoBase: IMAGE_INFO_BASE, + format: IMAGE_FORMAT.JPEG, + } + ); + expect(result).toEqual(MAGIC_BYTES.JPEG); }); - - it("SkImage.encodeToBase64: JPEG checking of the \"quality\" argument work", async () => { + it('SkImage.encodeToBase64: JPEG checking of the "quality" argument work', async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -109,7 +125,11 @@ describe("Image Encoding", () => { width, height, }; - const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; const minQuality = image.encodeToBase64(ctx.format, 0).length; const midQuality = image.encodeToBase64(ctx.format, 50).length; const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. @@ -121,8 +141,10 @@ describe("Image Encoding", () => { defaultQuality, maxQuality, }; - }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.JPEG }); - + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.JPEG } + ); + expect(result.minQuality).toBeLessThan(result.maxQuality); expect(result.minQuality).toBeLessThan(result.defaultQuality); expect(result.minQuality).toBeLessThan(result.midQuality); @@ -131,7 +153,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: PNG checking of the \"quality\" argument work", async () => { + it('SkImage.encodeToBase64: PNG checking of the "quality" argument work', async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -150,7 +172,11 @@ describe("Image Encoding", () => { width, height, }; - const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; const minQuality = image.encodeToBase64(ctx.format, 0).length; const midQuality = image.encodeToBase64(ctx.format, 50).length; const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. @@ -162,8 +188,10 @@ describe("Image Encoding", () => { defaultQuality, maxQuality, }; - }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG }); - + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } + ); + expect(result.minQuality).toBeLessThan(result.midQuality); expect(result.minQuality).toBeLessThan(result.defaultQuality); expect(result.minQuality).toBeLessThan(result.maxQuality); @@ -172,7 +200,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the \"quality\" argument work with default lossy - true", async () => { + it('SkImage.encodeToBase64: WEBP checking of the "quality" argument work with default lossy - true', async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -191,10 +219,18 @@ describe("Image Encoding", () => { width, height, }; - const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; const minQuality = image.encodeToBase64(ctx.format, 0, true).length; const midQuality = image.encodeToBase64(ctx.format, 50, true).length; - const defaultQuality = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, + true + ).length; // default quality: 100. const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; return { @@ -203,8 +239,10 @@ describe("Image Encoding", () => { defaultQuality, maxQuality, }; - }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); - + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); + expect(result.minQuality).toBeLessThan(result.midQuality); expect(result.minQuality).toBeLessThan(result.defaultQuality); expect(result.minQuality).toBeLessThan(result.maxQuality); @@ -213,7 +251,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the \"quality\" argument work with lossless", async () => { + it('SkImage.encodeToBase64: WEBP checking of the "quality" argument work with lossless', async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -232,10 +270,18 @@ describe("Image Encoding", () => { width, height, }; - const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; const minQuality = image.encodeToBase64(ctx.format, 0, false).length; const midQuality = image.encodeToBase64(ctx.format, 50, false).length; - const defaultQuality = image.encodeToBase64(ctx.format, undefined, false).length; // default quality: 100. + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, + false + ).length; // default quality: 100. const maxQuality = image.encodeToBase64(ctx.format, 100, false).length; return { @@ -244,8 +290,10 @@ describe("Image Encoding", () => { defaultQuality, maxQuality, }; - }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); - + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); + expect(result.minQuality).not.toEqual(result.midQuality); expect(result.minQuality).not.toEqual(result.defaultQuality); expect(result.minQuality).not.toEqual(result.maxQuality); @@ -254,7 +302,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the \"lossy\" argument work", async () => { + it('SkImage.encodeToBase64: WEBP checking of the "lossy" argument work', async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -273,18 +321,28 @@ describe("Image Encoding", () => { width, height, }; - const image = Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)!; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. - const lossless = image.encodeToBase64(ctx.format, undefined, false).length; // default quality: 100. + const lossless = image.encodeToBase64( + ctx.format, + undefined, + false + ).length; // default quality: 100. return { defaultLossy, lossy, lossless, }; - }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP }); - + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); + expect(result.lossy).toEqual(result.defaultLossy); expect(result.lossy).not.toEqual(result.lossless); expect(result.defaultLossy).not.toEqual(result.lossless); From d8961d390f6b3a71836e8a3db5a6965df93c1c5a Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Tue, 7 Nov 2023 00:25:09 +0300 Subject: [PATCH 13/20] Change JPEG min quality --- package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 1aad989520..36e37022e3 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -130,7 +130,7 @@ describe("Image Encoding", () => { data, width * bytesPerPixel )!; - const minQuality = image.encodeToBase64(ctx.format, 0).length; + const minQuality = image.encodeToBase64(ctx.format, 1e-8).length; // failed on CanvasKit with 0 quality. const midQuality = image.encodeToBase64(ctx.format, 50).length; const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. const maxQuality = image.encodeToBase64(ctx.format, 100).length; From c6652e568eb6505e38756f6c5c40127dae06073e Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Tue, 7 Nov 2023 00:38:16 +0300 Subject: [PATCH 14/20] Update bytes filling --- .../__tests__/e2e/ImageEncoding.spec.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 36e37022e3..00a2ea5969 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -114,8 +114,8 @@ describe("Image Encoding", () => { const bytes = new Uint8Array(width * height * bytesPerPixel); bytes.fill(255); let i = 0; - for (let x = 0; x < 256; x++) { - for (let y = 0; y < 256; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } } @@ -161,8 +161,8 @@ describe("Image Encoding", () => { const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); let i = 0; - for (let x = 0; x < 256; x++) { - for (let y = 0; y < 256; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } } @@ -208,8 +208,8 @@ describe("Image Encoding", () => { const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); let i = 0; - for (let x = 0; x < 256; x++) { - for (let y = 0; y < 256; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } } @@ -259,8 +259,8 @@ describe("Image Encoding", () => { const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); let i = 0; - for (let x = 0; x < 256; x++) { - for (let y = 0; y < 256; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } } @@ -310,8 +310,8 @@ describe("Image Encoding", () => { const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); let i = 0; - for (let x = 0; x < 256; x++) { - for (let y = 0; y < 256; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } } From d602fa755cc9ea323c1f668bc0c94b9a4c5d8947 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Tue, 7 Nov 2023 02:47:22 +0300 Subject: [PATCH 15/20] Fix quotes --- .../__tests__/e2e/ImageEncoding.spec.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 00a2ea5969..1a7325ff8c 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -105,7 +105,7 @@ describe("Image Encoding", () => { expect(result).toEqual(MAGIC_BYTES.JPEG); }); - it('SkImage.encodeToBase64: JPEG checking of the "quality" argument work', async () => { + it("SkImage.encodeToBase64: JPEG checking of the quality argument work", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -153,7 +153,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it('SkImage.encodeToBase64: PNG checking of the "quality" argument work', async () => { + it("SkImage.encodeToBase64: PNG checking of the quality argument work", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -200,7 +200,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it('SkImage.encodeToBase64: WEBP checking of the "quality" argument work with default lossy - true', async () => { + it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -228,9 +228,9 @@ describe("Image Encoding", () => { const midQuality = image.encodeToBase64(ctx.format, 50, true).length; const defaultQuality = image.encodeToBase64( ctx.format, - undefined, + undefined, // default quality: 100. true - ).length; // default quality: 100. + ).length; const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; return { @@ -251,7 +251,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it('SkImage.encodeToBase64: WEBP checking of the "quality" argument work with lossless', async () => { + it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -302,7 +302,7 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it('SkImage.encodeToBase64: WEBP checking of the "lossy" argument work', async () => { + it("SkImage.encodeToBase64: WEBP checking of the lossy argument work", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -330,9 +330,9 @@ describe("Image Encoding", () => { const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. const lossless = image.encodeToBase64( ctx.format, - undefined, + undefined, // default quality: 100. false - ).length; // default quality: 100. + ).length; return { defaultLossy, From c90d7dd83a2265799c56388110ceb63e95c66464 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 19 Nov 2023 21:24:03 +0100 Subject: [PATCH 16/20] fix test suite --- .../__tests__/e2e/ImageEncoding.spec.tsx | 435 +++++++++--------- package/src/skia/web/JsiSkImage.ts | 5 +- 2 files changed, 232 insertions(+), 208 deletions(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 1a7325ff8c..9f9b47fb75 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -1,3 +1,4 @@ +import { itRunsE2eOnly } from "../../../__tests__/setup"; import { surface } from "../setup"; const MAGIC_BYTES = { @@ -18,34 +19,37 @@ const IMAGE_INFO_BASE = { }; describe("Image Encoding", () => { - it("SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1; - const height = 1; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - bytes.fill(255); - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; + itRunsE2eOnly( + "SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", + async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1; + const height = 1; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; - return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! - .encodeToBase64(ctx.format) - .slice(0, ctx.cutIndex); - }, - { - cutIndex: MAGIC_BYTES.WEBP.length, - imageInfoBase: IMAGE_INFO_BASE, - format: IMAGE_FORMAT.WEBP, - } - ); + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! + .encodeToBase64(ctx.format) + .slice(0, ctx.cutIndex); + }, + { + cutIndex: MAGIC_BYTES.WEBP.length, + imageInfoBase: IMAGE_INFO_BASE, + format: IMAGE_FORMAT.WEBP, + } + ); - expect(result).toEqual(MAGIC_BYTES.WEBP); - }); + expect(result).toEqual(MAGIC_BYTES.WEBP); + } + ); it("SkImage.encodeToBase64: check PNG format by magic bytes sequence iVBORw0KGgo...", async () => { const result = await surface.eval( @@ -130,9 +134,9 @@ describe("Image Encoding", () => { data, width * bytesPerPixel )!; - const minQuality = image.encodeToBase64(ctx.format, 1e-8).length; // failed on CanvasKit with 0 quality. + const minQuality = image.encodeToBase64(ctx.format, 1e-8).length; const midQuality = image.encodeToBase64(ctx.format, 50).length; - const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const defaultQuality = image.encodeToBase64(ctx.format).length; const maxQuality = image.encodeToBase64(ctx.format, 100).length; return { @@ -153,198 +157,215 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: PNG checking of the quality argument work", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; + itRunsE2eOnly( + "SkImage.encodeToBase64: PNG checking of the quality argument work", + async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; + } } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0).length; - const midQuality = image.encodeToBase64(ctx.format, 50).length; - const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. - const maxQuality = image.encodeToBase64(ctx.format, 100).length; + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0).length; + const midQuality = image.encodeToBase64(ctx.format, 50).length; + const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } + ); - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - }); + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + } + ); - // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; + itRunsE2eOnly( + "SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", + async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; + } } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0, true).length; - const midQuality = image.encodeToBase64(ctx.format, 50, true).length; - const defaultQuality = image.encodeToBase64( - ctx.format, - undefined, // default quality: 100. - true - ).length; - const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0, true).length; + const midQuality = image.encodeToBase64(ctx.format, 50, true).length; + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, // default quality: 100. + true + ).length; + const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - }); + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + } + ); - // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; + itRunsE2eOnly( + "SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", + async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; + } } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0, false).length; - const midQuality = image.encodeToBase64(ctx.format, 50, false).length; - const defaultQuality = image.encodeToBase64( - ctx.format, - undefined, - false - ).length; // default quality: 100. - const maxQuality = image.encodeToBase64(ctx.format, 100, false).length; + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0, false).length; + const midQuality = image.encodeToBase64(ctx.format, 50, false).length; + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, + false + ).length; // default quality: 100. + const maxQuality = image.encodeToBase64( + ctx.format, + 100, + false + ).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.minQuality).not.toEqual(result.midQuality); - expect(result.minQuality).not.toEqual(result.defaultQuality); - expect(result.minQuality).not.toEqual(result.maxQuality); - expect(result.midQuality).not.toEqual(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - }); + expect(result.minQuality).not.toEqual(result.midQuality); + expect(result.minQuality).not.toEqual(result.defaultQuality); + expect(result.minQuality).not.toEqual(result.maxQuality); + expect(result.midQuality).not.toEqual(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + } + ); - // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: WEBP checking of the lossy argument work", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; + itRunsE2eOnly( + "SkImage.encodeToBase64: WEBP checking of the lossy argument work", + async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; + } } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. - const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. - const lossless = image.encodeToBase64( - ctx.format, - undefined, // default quality: 100. - false - ).length; + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. + const lossy = image.encodeToBase64( + ctx.format, + undefined, + true + ).length; // default quality: 100. + const lossless = image.encodeToBase64( + ctx.format, + undefined, // default quality: 100. + false + ).length; - return { - defaultLossy, - lossy, - lossless, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + defaultLossy, + lossy, + lossless, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.lossy).toEqual(result.defaultLossy); - expect(result.lossy).not.toEqual(result.lossless); - expect(result.defaultLossy).not.toEqual(result.lossless); - }); + expect(result.lossy).toEqual(result.defaultLossy); + expect(result.lossy).not.toEqual(result.lossless); + expect(result.defaultLossy).not.toEqual(result.lossless); + } + ); }); diff --git a/package/src/skia/web/JsiSkImage.ts b/package/src/skia/web/JsiSkImage.ts index dcb7403d66..dda573448c 100644 --- a/package/src/skia/web/JsiSkImage.ts +++ b/package/src/skia/web/JsiSkImage.ts @@ -1,7 +1,6 @@ import type { CanvasKit, Image } from "canvaskit-wasm"; import type { - ImageFormat, FilterMode, MipmapMode, SkImage, @@ -9,6 +8,7 @@ import type { SkShader, TileMode, } from "../types"; +import { ImageFormat } from "../types"; import { ckEnum, HostObject } from "./Host"; import { JsiSkMatrix } from "./JsiSkMatrix"; @@ -91,6 +91,9 @@ export class JsiSkImage extends HostObject implements SkImage { } encodeToBytes(fmt?: ImageFormat, quality?: number) { + if (fmt === ImageFormat.WEBP) { + throw new Error("WEBP format is not supported on web"); + } let result: Uint8Array | null; if (fmt && quality) { result = this.ref.encodeToBytes(ckEnum(fmt), quality); From 88aa7c477d8488e0a1f7ed561fe1067de3dcd94a Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 19 Nov 2023 21:31:46 +0100 Subject: [PATCH 17/20] Revert changes to the test suite --- .../__tests__/e2e/ImageEncoding.spec.tsx | 428 +++++++++--------- package/src/skia/web/JsiSkImage.ts | 5 +- 2 files changed, 203 insertions(+), 230 deletions(-) diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index 9f9b47fb75..a370472ed1 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -1,4 +1,3 @@ -import { itRunsE2eOnly } from "../../../__tests__/setup"; import { surface } from "../setup"; const MAGIC_BYTES = { @@ -19,37 +18,34 @@ const IMAGE_INFO_BASE = { }; describe("Image Encoding", () => { - itRunsE2eOnly( - "SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", - async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1; - const height = 1; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - bytes.fill(255); - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; + it("SkImage.encodeToBase64: check WEBP format by magic bytes sequence UklGR...", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1; + const height = 1; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; - return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! - .encodeToBase64(ctx.format) - .slice(0, ctx.cutIndex); - }, - { - cutIndex: MAGIC_BYTES.WEBP.length, - imageInfoBase: IMAGE_INFO_BASE, - format: IMAGE_FORMAT.WEBP, - } - ); + return Skia.Image.MakeImage(imageInfo, data, width * bytesPerPixel)! + .encodeToBase64(ctx.format) + .slice(0, ctx.cutIndex); + }, + { + cutIndex: MAGIC_BYTES.WEBP.length, + imageInfoBase: IMAGE_INFO_BASE, + format: IMAGE_FORMAT.WEBP, + } + ); - expect(result).toEqual(MAGIC_BYTES.WEBP); - } - ); + expect(result).toEqual(MAGIC_BYTES.WEBP); + }); it("SkImage.encodeToBase64: check PNG format by magic bytes sequence iVBORw0KGgo...", async () => { const result = await surface.eval( @@ -157,215 +153,195 @@ describe("Image Encoding", () => { }); // this test can be failed on CanvasKit - itRunsE2eOnly( - "SkImage.encodeToBase64: PNG checking of the quality argument work", - async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } + it("SkImage.encodeToBase64: PNG checking of the quality argument work", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0).length; - const midQuality = image.encodeToBase64(ctx.format, 50).length; - const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. - const maxQuality = image.encodeToBase64(ctx.format, 100).length; + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0).length; + const midQuality = image.encodeToBase64(ctx.format, 50).length; + const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } + ); - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - } - ); + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); - itRunsE2eOnly( - "SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", - async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } + it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0, true).length; - const midQuality = image.encodeToBase64(ctx.format, 50, true).length; - const defaultQuality = image.encodeToBase64( - ctx.format, - undefined, // default quality: 100. - true - ).length; - const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0, true).length; + const midQuality = image.encodeToBase64(ctx.format, 50, true).length; + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, // default quality: 100. + true + ).length; + const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - } - ); + expect(result.minQuality).toBeLessThan(result.midQuality); + expect(result.minQuality).toBeLessThan(result.defaultQuality); + expect(result.minQuality).toBeLessThan(result.maxQuality); + expect(result.midQuality).toBeLessThan(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); - itRunsE2eOnly( - "SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", - async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } + it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0, false).length; - const midQuality = image.encodeToBase64(ctx.format, 50, false).length; - const defaultQuality = image.encodeToBase64( - ctx.format, - undefined, - false - ).length; // default quality: 100. - const maxQuality = image.encodeToBase64( - ctx.format, - 100, - false - ).length; + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const minQuality = image.encodeToBase64(ctx.format, 0, false).length; + const midQuality = image.encodeToBase64(ctx.format, 50, false).length; + const defaultQuality = image.encodeToBase64( + ctx.format, + undefined, + false + ).length; // default quality: 100. + const maxQuality = image.encodeToBase64(ctx.format, 100, false).length; - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + minQuality, + midQuality, + defaultQuality, + maxQuality, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.minQuality).not.toEqual(result.midQuality); - expect(result.minQuality).not.toEqual(result.defaultQuality); - expect(result.minQuality).not.toEqual(result.maxQuality); - expect(result.midQuality).not.toEqual(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - } - ); + expect(result.minQuality).not.toEqual(result.midQuality); + expect(result.minQuality).not.toEqual(result.defaultQuality); + expect(result.minQuality).not.toEqual(result.maxQuality); + expect(result.midQuality).not.toEqual(result.maxQuality); + expect(result.defaultQuality).toEqual(result.maxQuality); + }); - itRunsE2eOnly( - "SkImage.encodeToBase64: WEBP checking of the lossy argument work", - async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } + it("SkImage.encodeToBase64: WEBP checking of the lossy argument work", async () => { + const result = await surface.eval( + (Skia, ctx) => { + const width = 1024; + const height = 1024; + const bytesPerPixel = 4; + const bytes = new Uint8Array(width * height * bytesPerPixel); + let i = 0; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + bytes[i++] = (x * y) % 255; } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. - const lossy = image.encodeToBase64( - ctx.format, - undefined, - true - ).length; // default quality: 100. - const lossless = image.encodeToBase64( - ctx.format, - undefined, // default quality: 100. - false - ).length; + } + const data = Skia.Data.fromBytes(bytes); + const imageInfo = { + ...ctx.imageInfoBase, + width, + height, + }; + const image = Skia.Image.MakeImage( + imageInfo, + data, + width * bytesPerPixel + )!; + const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. + const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. + const lossless = image.encodeToBase64( + ctx.format, + undefined, // default quality: 100. + false + ).length; - return { - defaultLossy, - lossy, - lossless, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); + return { + defaultLossy, + lossy, + lossless, + }; + }, + { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } + ); - expect(result.lossy).toEqual(result.defaultLossy); - expect(result.lossy).not.toEqual(result.lossless); - expect(result.defaultLossy).not.toEqual(result.lossless); - } - ); + expect(result.lossy).toEqual(result.defaultLossy); + expect(result.lossy).not.toEqual(result.lossless); + expect(result.defaultLossy).not.toEqual(result.lossless); + }); }); diff --git a/package/src/skia/web/JsiSkImage.ts b/package/src/skia/web/JsiSkImage.ts index dda573448c..1fbdca1634 100644 --- a/package/src/skia/web/JsiSkImage.ts +++ b/package/src/skia/web/JsiSkImage.ts @@ -7,8 +7,8 @@ import type { SkMatrix, SkShader, TileMode, + ImageFormat, } from "../types"; -import { ImageFormat } from "../types"; import { ckEnum, HostObject } from "./Host"; import { JsiSkMatrix } from "./JsiSkMatrix"; @@ -91,9 +91,6 @@ export class JsiSkImage extends HostObject implements SkImage { } encodeToBytes(fmt?: ImageFormat, quality?: number) { - if (fmt === ImageFormat.WEBP) { - throw new Error("WEBP format is not supported on web"); - } let result: Uint8Array | null; if (fmt && quality) { result = this.ref.encodeToBytes(ckEnum(fmt), quality); From 7c43322569807e7456c8b9c523d3775ae2bf82ba Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 20 Nov 2023 14:18:59 +0300 Subject: [PATCH 18/20] Align code to match with the CanvasKit logic, update e2e tests --- package/cpp/api/JsiSkImage.h | 26 +-- .../__tests__/e2e/ImageEncoding.spec.tsx | 152 ++++-------------- package/src/skia/types/Image/Image.ts | 11 +- 3 files changed, 40 insertions(+), 149 deletions(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index cc23e6d740..20537c3bca 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -28,11 +28,6 @@ namespace RNSkia { namespace jsi = facebook::jsi; class JsiSkImage : public JsiSkWrappingSkPtrHostObject { -private: - double lerp(double a, double b, double t) { - return (a * (1.0 - t)) + (b * t); - } - public: // TODO-API: Properties? JSI_HOST_FUNCTION(width) { return static_cast(getObject()->width()); } @@ -90,22 +85,17 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { options.fQuality = quality; data = SkJpegEncoder::Encode(nullptr, image.get(), options); } else if (format == SkEncodedImageFormat::kWEBP) { - const bool lossy = - (count >= 3 && arguments[2].isBool()) ? arguments[2].asBool() : true; - SkWebpEncoder::Options options; - options.fQuality = quality; - options.fCompression = lossy ? SkWebpEncoder::Compression::kLossy - : SkWebpEncoder::Compression::kLossless; + if (quality >= 100) { + options.fCompression = SkWebpEncoder::Compression::kLossless; + options.fQuality = 75; // This is effort to compress + } else { + options.fCompression = SkWebpEncoder::Compression::kLossy; + options.fQuality = quality; + } data = SkWebpEncoder::Encode(nullptr, image.get(), options); } else { - const double t = quality / 100.0; - const int level = static_cast(std::round(lerp(9.0, 0.0, t))); - - SkPngEncoder::Options options; - // must be in [0, 9] where 9 corresponds to maximal compression. - options.fZLibLevel = level; - data = SkPngEncoder::Encode(nullptr, image.get(), options); + data = SkPngEncoder::Encode(nullptr, image.get(), {}); } return data; diff --git a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx index a370472ed1..1785f6239b 100644 --- a/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx +++ b/package/src/renderer/__tests__/e2e/ImageEncoding.spec.tsx @@ -114,7 +114,7 @@ describe("Image Encoding", () => { const bytes = new Uint8Array(width * height * bytesPerPixel); bytes.fill(255); let i = 0; - for (let x = 0; x < width; x++) { + for (let x = 0; x < width * bytesPerPixel; x++) { for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } @@ -152,16 +152,16 @@ describe("Image Encoding", () => { expect(result.defaultQuality).toEqual(result.maxQuality); }); - // this test can be failed on CanvasKit - it("SkImage.encodeToBase64: PNG checking of the quality argument work", async () => { + it("SkImage.encodeToBase64: PNG checking. The quality argument doesn't work", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; const height = 1024; const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); + bytes.fill(255); let i = 0; - for (let x = 0; x < width; x++) { + for (let x = 0; x < width * bytesPerPixel; x++) { for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } @@ -177,9 +177,9 @@ describe("Image Encoding", () => { data, width * bytesPerPixel )!; - const minQuality = image.encodeToBase64(ctx.format, 0).length; + const minQuality = image.encodeToBase64(ctx.format, 1e-8).length; const midQuality = image.encodeToBase64(ctx.format, 50).length; - const defaultQuality = image.encodeToBase64(ctx.format).length; // default quality: 100. + const defaultQuality = image.encodeToBase64(ctx.format).length; const maxQuality = image.encodeToBase64(ctx.format, 100).length; return { @@ -192,14 +192,12 @@ describe("Image Encoding", () => { { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.PNG } ); - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); + expect(result.minQuality).toEqual(result.midQuality); + expect(result.minQuality).toEqual(result.defaultQuality); + expect(result.minQuality).toEqual(result.maxQuality); }); - it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossy", async () => { + it("SkImage.encodeToBase64: WEBP checking of the quality argument work", async () => { const result = await surface.eval( (Skia, ctx) => { const width = 1024; @@ -207,7 +205,7 @@ describe("Image Encoding", () => { const bytesPerPixel = 4; const bytes = new Uint8Array(width * height * bytesPerPixel); let i = 0; - for (let x = 0; x < width; x++) { + for (let x = 0; x < width * bytesPerPixel; x++) { for (let y = 0; y < height; y++) { bytes[i++] = (x * y) % 255; } @@ -223,125 +221,35 @@ describe("Image Encoding", () => { data, width * bytesPerPixel )!; - const minQuality = image.encodeToBase64(ctx.format, 0, true).length; - const midQuality = image.encodeToBase64(ctx.format, 50, true).length; - const defaultQuality = image.encodeToBase64( + const minQualityLossy = image.encodeToBase64(ctx.format, 1e-8).length; + const midQualityLossy = image.encodeToBase64(ctx.format, 50).length; + const maxQualityLossy = image.encodeToBase64( ctx.format, - undefined, // default quality: 100. - true + 100 - 1e-8 ).length; - const maxQuality = image.encodeToBase64(ctx.format, 100, true).length; - - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); - - expect(result.minQuality).toBeLessThan(result.midQuality); - expect(result.minQuality).toBeLessThan(result.defaultQuality); - expect(result.minQuality).toBeLessThan(result.maxQuality); - expect(result.midQuality).toBeLessThan(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - }); - - it("SkImage.encodeToBase64: WEBP checking of the quality argument work with lossless", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const minQuality = image.encodeToBase64(ctx.format, 0, false).length; - const midQuality = image.encodeToBase64(ctx.format, 50, false).length; - const defaultQuality = image.encodeToBase64( - ctx.format, - undefined, - false - ).length; // default quality: 100. - const maxQuality = image.encodeToBase64(ctx.format, 100, false).length; - - return { - minQuality, - midQuality, - defaultQuality, - maxQuality, - }; - }, - { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } - ); - - expect(result.minQuality).not.toEqual(result.midQuality); - expect(result.minQuality).not.toEqual(result.defaultQuality); - expect(result.minQuality).not.toEqual(result.maxQuality); - expect(result.midQuality).not.toEqual(result.maxQuality); - expect(result.defaultQuality).toEqual(result.maxQuality); - }); - - it("SkImage.encodeToBase64: WEBP checking of the lossy argument work", async () => { - const result = await surface.eval( - (Skia, ctx) => { - const width = 1024; - const height = 1024; - const bytesPerPixel = 4; - const bytes = new Uint8Array(width * height * bytesPerPixel); - let i = 0; - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - bytes[i++] = (x * y) % 255; - } - } - const data = Skia.Data.fromBytes(bytes); - const imageInfo = { - ...ctx.imageInfoBase, - width, - height, - }; - const image = Skia.Image.MakeImage( - imageInfo, - data, - width * bytesPerPixel - )!; - const defaultLossy = image.encodeToBase64(ctx.format).length; // default quality: 100. - const lossy = image.encodeToBase64(ctx.format, undefined, true).length; // default quality: 100. - const lossless = image.encodeToBase64( + const defaultQualityLossless = image.encodeToBase64( ctx.format, - undefined, // default quality: 100. - false + undefined ).length; + const maxQualityLossless = image.encodeToBase64(ctx.format, 100).length; return { - defaultLossy, - lossy, - lossless, + minQualityLossy, + midQualityLossy, + maxQualityLossy, + defaultQualityLossless, + maxQualityLossless, }; }, { imageInfoBase: IMAGE_INFO_BASE, format: IMAGE_FORMAT.WEBP } ); - expect(result.lossy).toEqual(result.defaultLossy); - expect(result.lossy).not.toEqual(result.lossless); - expect(result.defaultLossy).not.toEqual(result.lossless); + expect(result.minQualityLossy).toBeLessThan(result.midQualityLossy); + expect(result.minQualityLossy).toBeLessThan(result.maxQualityLossy); + expect(result.midQualityLossy).toBeLessThan(result.maxQualityLossy); + expect(result.minQualityLossy).not.toEqual(result.maxQualityLossless); + expect(result.midQualityLossy).not.toEqual(result.maxQualityLossless); + expect(result.maxQualityLossy).not.toEqual(result.maxQualityLossless); + expect(result.defaultQualityLossless).toEqual(result.maxQualityLossless); }); }); diff --git a/package/src/skia/types/Image/Image.ts b/package/src/skia/types/Image/Image.ts index 117f1b1c47..a6a1ef5321 100644 --- a/package/src/skia/types/Image/Image.ts +++ b/package/src/skia/types/Image/Image.ts @@ -74,16 +74,10 @@ export interface SkImage extends SkJSIInstance<"Image"> { @param fmt - PNG is the default value. @param quality - a value from 0 to 100; 100 is the least lossy. May be ignored. - @param lossy - WEBP lossy compression flag, default value is true. May be ignored. - @return Uint8Array with data */ - encodeToBytes( - fmt?: ImageFormat, - quality?: number, - lossy?: boolean - ): Uint8Array; + encodeToBytes(fmt?: ImageFormat, quality?: number): Uint8Array; /** Encodes Image pixels, returning result as a base64 encoded string. Returns existing encoded data if present; otherwise, SkImage is encoded with @@ -95,11 +89,10 @@ export interface SkImage extends SkJSIInstance<"Image"> { @param fmt - PNG is the default value. @param quality - a value from 0 to 100; 100 is the least lossy. May be ignored. - @param lossy - WEBP lossy compression flag, default value is true. May be ignored. @return base64 encoded string of data */ - encodeToBase64(fmt?: ImageFormat, quality?: number, lossy?: boolean): string; + encodeToBase64(fmt?: ImageFormat, quality?: number): string; /** * Returns raster image or lazy image. Copies SkImage backed by GPU texture From cf450fc258fd00d9f67d7ee645b0af630a18adad Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 20 Nov 2023 14:33:06 +0300 Subject: [PATCH 19/20] Revert PNG options --- package/cpp/api/JsiSkImage.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/cpp/api/JsiSkImage.h b/package/cpp/api/JsiSkImage.h index 20537c3bca..fe2007bae6 100644 --- a/package/cpp/api/JsiSkImage.h +++ b/package/cpp/api/JsiSkImage.h @@ -95,7 +95,8 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject { } data = SkWebpEncoder::Encode(nullptr, image.get(), options); } else { - data = SkPngEncoder::Encode(nullptr, image.get(), {}); + SkPngEncoder::Options options; + data = SkPngEncoder::Encode(nullptr, image.get(), options); } return data; From 7fefeff2ef2352f0112ae5cb965a3c2c2fb7187e Mon Sep 17 00:00:00 2001 From: William Candillon Date: Mon, 20 Nov 2023 13:48:42 +0100 Subject: [PATCH 20/20] Fix merge conflict --- package/src/skia/web/JsiSkImage.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package/src/skia/web/JsiSkImage.ts b/package/src/skia/web/JsiSkImage.ts index faa29c88ee..8f61ef04cc 100644 --- a/package/src/skia/web/JsiSkImage.ts +++ b/package/src/skia/web/JsiSkImage.ts @@ -1,3 +1,9 @@ +import type { + CanvasKit, + ImageInfo as CKImageInfo, + Image, +} from "canvaskit-wasm"; + import type { FilterMode, MipmapMode, @@ -6,6 +12,7 @@ import type { SkShader, TileMode, ImageFormat, + ImageInfo, } from "../types"; import { ckEnum, getCkEnum, HostObject } from "./Host";