diff --git a/README.md b/README.md index 991c7c76..906f985a 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,21 @@ const imageData = await image.toEncodedImageData('jpg', 90) const sameImageCopied = await Images.loadFromEncodedImageData(imageData) ``` +#### Base64 (`string`) + +The `Image` type can be encoded to a standard Base64 string using a container format like `jpg`, `png` or `heic`: + +```ts +const image = ... +const base64 = await image.toBase64Async('jpg', 90) +const dataUrl = `data:image/jpeg;base64,${base64}` +``` + +`toBase64Async(...)` returns a padded Base64 payload without line breaks or a data URL prefix. + +> [!NOTE] +> Base64 is larger than binary image data. Prefer `toEncodedImageDataAsync(...)` unless the destination specifically requires a string. + #### Resizing An `Image` can be resized entirely in-memory, without ever writing to- or reading from- a file: @@ -226,6 +241,7 @@ Images can be compressed using the `jpg` container format - either in-memory or const image = ... const path = await image.saveToTemporaryFileAsync('jpg', 50) // 50% compression const compressed = await image.toEncodedImageData('jpg', 50) // 50% compression +const base64 = await image.toBase64Async('jpg', 50) // 50% compression ``` #### HEIC/HEIF diff --git a/example/__tests__/image-encoding.harness.ts b/example/__tests__/image-encoding.harness.ts index f02f2d0a..28404008 100644 --- a/example/__tests__/image-encoding.harness.ts +++ b/example/__tests__/image-encoding.harness.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'react-native-harness' -import { Images } from 'react-native-nitro-image' +import { + Images, + thumbHashFromBase64String, + thumbHashToBase64String, +} from 'react-native-nitro-image' const makeImage = () => Images.createBlankImage(16, 16, false, { r: 0, g: 0, b: 1, a: 1 }) @@ -110,6 +114,69 @@ describe('Image - toEncodedImageData', () => { }) }) +describe('Image - toBase64Async', () => { + it('encodes PNG to a bare standard Base64 string', async () => { + const image = makeImage() + const base64 = await image.toBase64Async('png') + + expect(base64.length).toBeGreaterThan(0) + expect(base64.startsWith('data:')).toBe(false) + expect(base64.length % 4).toBe(0) + expect(base64).toMatch(/^[A-Za-z0-9+/]*={0,2}$/) + expect(base64.startsWith('iVBORw0KGgo')).toBe(true) + }) + + it('matches toEncodedImageDataAsync for the same format', async () => { + const image = makeImage() + const base64 = await image.toBase64Async('png') + const encoded = await image.toEncodedImageDataAsync('png') + + expect(base64).toBe(thumbHashToBase64String(encoded.buffer)) + }) + + it('decodes to valid encoded image bytes', async () => { + const image = makeImage() + const base64 = await image.toBase64Async('png') + const buffer = thumbHashFromBase64String(base64) + const decoded = Images.loadFromEncodedImageData({ + buffer, + width: image.width, + height: image.height, + imageFormat: 'png', + }) + + expect(decoded.width).toBe(image.width) + expect(decoded.height).toBe(image.height) + }) + + it('uses a 0...100 JPEG quality range with 100 as the default', async () => { + const image = makeDetailedImage() + const lowest = await image.toBase64Async('jpg', 0) + const highest = await image.toBase64Async('jpg', 100) + const defaultQuality = await image.toBase64Async('jpg') + + expect(lowest.length).toBeLessThan(highest.length) + expect(defaultQuality).toBe(highest) + }) + + it('rounds fractional JPEG quality to the nearest integer', async () => { + const image = makeDetailedImage() + const quality0 = await image.toBase64Async('jpg', 0) + const quality0Point4 = await image.toBase64Async('jpg', 0.4) + const quality99Point5 = await image.toBase64Async('jpg', 99.5) + const quality100 = await image.toBase64Async('jpg', 100) + + expect(quality0Point4).toBe(quality0) + expect(quality99Point5).toBe(quality100) + }) + + it('rejects quality values outside 0...100', async () => { + const image = makeImage() + await expect(image.toBase64Async('jpg', -0.5)).rejects.toBeDefined() + await expect(image.toBase64Async('jpg', 100.5)).rejects.toBeDefined() + }) +}) + describe('Images - loadFromEncodedImageData', () => { it('round-trips through PNG encoding', () => { const image = makeImage() diff --git a/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImage.kt b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImage.kt index 1a728972..e45c412d 100644 --- a/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImage.kt +++ b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImage.kt @@ -20,6 +20,7 @@ import com.margelo.nitro.image.extensions.pixelFormat import com.margelo.nitro.image.extensions.saveToFile import com.margelo.nitro.image.extensions.toFilePath import com.margelo.nitro.image.extensions.toByteBuffer +import com.margelo.nitro.image.extensions.toBase64 import com.margelo.nitro.image.extensions.toCpuAccessible import com.margelo.nitro.image.extensions.toMutable import java.io.File @@ -92,6 +93,13 @@ class HybridImage: HybridImageSpec { return Promise.async { toEncodedImageData(format, quality) } } + override fun toBase64Async(format: ImageFormat, quality: Double?): Promise { + return Promise.parallel { + val resolvedQuality = resolveImageQuality(quality) + bitmap.toBase64(format, resolvedQuality) + } + } + override fun rotate(degrees: Double, allowFastFlagRotation: Boolean?): HybridImageSpec { // 1. Make sure the Bitmap we want to draw is drawable (HARDWARE isn't) val source = bitmap.toCpuAccessible() diff --git a/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/extensions/Bitmap+toBase64.kt b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/extensions/Bitmap+toBase64.kt new file mode 100644 index 00000000..47441319 --- /dev/null +++ b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/extensions/Bitmap+toBase64.kt @@ -0,0 +1,14 @@ +package com.margelo.nitro.image.extensions + +import android.graphics.Bitmap +import com.margelo.nitro.image.ImageFormat +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +@OptIn(ExperimentalEncodingApi::class) +internal fun Bitmap.toBase64(format: ImageFormat, quality: Int): String { + val buffer = compressInMemory(format, quality) + val startIndex = buffer.arrayOffset() + buffer.position() + val endIndex = buffer.arrayOffset() + buffer.limit() + return Base64.Default.encode(buffer.array(), startIndex, endIndex) +} diff --git a/packages/react-native-nitro-image/ios/Extensions/UIImage+toBase64.swift b/packages/react-native-nitro-image/ios/Extensions/UIImage+toBase64.swift new file mode 100644 index 00000000..c31e9153 --- /dev/null +++ b/packages/react-native-nitro-image/ios/Extensions/UIImage+toBase64.swift @@ -0,0 +1,18 @@ +// +// UIImage+toBase64.swift +// react-native-nitro-image +// +// Created by Marc Rousavy on 25.08.26. +// + +import UIKit + +extension UIImage { + /** + * Encodes this Image in the given `format` and returns its Base64 string. + */ + func toBase64(format: ImageFormat, quality: Double) throws -> String { + let data = try getData(in: format, quality: quality) + return data.base64EncodedString() + } +} diff --git a/packages/react-native-nitro-image/ios/Public/NativeImage.swift b/packages/react-native-nitro-image/ios/Public/NativeImage.swift index 4532e429..32a45ef4 100644 --- a/packages/react-native-nitro-image/ios/Public/NativeImage.swift +++ b/packages/react-native-nitro-image/ios/Public/NativeImage.swift @@ -60,6 +60,14 @@ public extension NativeImage { } } + func toBase64Async(format: ImageFormat, quality: Double?) -> Promise { + let image = uiImage + let resolvedQuality = quality ?? 100 + return Promise.parallel(.global(qos: .userInitiated)) { + return try image.toBase64(format: format, quality: resolvedQuality) + } + } + func rotate(degrees: Double, allowFastFlagRotation: Bool?) -> any HybridImageSpec { if allowFastFlagRotation == true, degrees.truncatingRemainder(dividingBy: 90) == 0, diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.cpp index 9bec1805..32cf3a20 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.cpp @@ -30,11 +30,11 @@ namespace margelo::nitro::image { class HybridImageSpec; } #include "JEncodedImageData.hpp" #include "ImageFormat.hpp" #include "JImageFormat.hpp" +#include #include #include "HybridImageSpec.hpp" #include "JHybridImageSpec.hpp" #include -#include #include namespace margelo::nitro::image { @@ -121,6 +121,22 @@ namespace margelo::nitro::image { return __promise; }(); } + std::shared_ptr> JHybridImageSpec::toBase64Async(ImageFormat format, std::optional quality) { + static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* format */, jni::alias_ref /* quality */)>("toBase64Async"); + auto __result = method(_javaPart, JImageFormat::fromCpp(format), quality.has_value() ? jni::JDouble::valueOf(quality.value()) : nullptr); + return [&]() { + auto __promise = Promise::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve(__result->toStdString()); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }(); + } std::shared_ptr JHybridImageSpec::resize(double width, double height) { static const auto method = _javaPart->javaClassStatic()->getMethod(double /* width */, double /* height */)>("resize"); auto __result = method(_javaPart, width, height); diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.hpp index 88e7488a..9644d27f 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.hpp @@ -59,6 +59,7 @@ namespace margelo::nitro::image { std::shared_ptr> toRawPixelDataAsync(std::optional allowGpu) override; EncodedImageData toEncodedImageData(ImageFormat format, std::optional quality) override; std::shared_ptr> toEncodedImageDataAsync(ImageFormat format, std::optional quality) override; + std::shared_ptr> toBase64Async(ImageFormat format, std::optional quality) override; std::shared_ptr resize(double width, double height) override; std::shared_ptr>> resizeAsync(double width, double height) override; std::shared_ptr rotate(double degrees, std::optional allowFastFlagRotation) override; diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageSpec.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageSpec.kt index 19fa6f32..48d16c32 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageSpec.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageSpec.kt @@ -53,6 +53,10 @@ abstract class HybridImageSpec: HybridObject() { @Keep abstract fun toEncodedImageDataAsync(format: ImageFormat, quality: Double?): Promise + @DoNotStrip + @Keep + abstract fun toBase64Async(format: ImageFormat, quality: Double?): Promise + @DoNotStrip @Keep abstract fun resize(width: Double, height: Double): HybridImageSpec diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.cpp b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.cpp index fa0199d5..a420a1e0 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.cpp @@ -43,6 +43,14 @@ namespace margelo::nitro::image::bridge::swift { }; } + // pragma MARK: std::function + Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = NitroImage::Func_void_std__string::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const std::string& result) mutable -> void { + swiftClosure.call(result); + }; + } + // pragma MARK: std::shared_ptr std::shared_ptr create_std__shared_ptr_HybridImageSpec_(void* NON_NULL swiftUnsafePointer) noexcept { NitroImage::HybridImageSpec_cxx swiftPart = NitroImage::HybridImageSpec_cxx::fromUnsafe(swiftUnsafePointer); @@ -75,14 +83,6 @@ namespace margelo::nitro::image::bridge::swift { }; } - // pragma MARK: std::function - Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept { - auto swiftClosure = NitroImage::Func_void_std__string::fromUnsafe(swiftClosureWrapper); - return [swiftClosure = std::move(swiftClosure)](const std::string& result) mutable -> void { - swiftClosure.call(result); - }; - } - // pragma MARK: std::function& /* result */)> Func_void_std__shared_ptr_ArrayBuffer_ create_Func_void_std__shared_ptr_ArrayBuffer_(void* NON_NULL swiftClosureWrapper) noexcept { auto swiftClosure = NitroImage::Func_void_std__shared_ptr_ArrayBuffer_::fromUnsafe(swiftClosureWrapper); diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp index 9b216f61..305064ff 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp @@ -200,6 +200,40 @@ namespace margelo::nitro::image::bridge::swift { return Func_void_EncodedImageData_Wrapper(std::move(value)); } + // pragma MARK: std::shared_ptr> + /** + * Specialized version of `std::shared_ptr>`. + */ + using std__shared_ptr_Promise_std__string__ = std::shared_ptr>; + inline std::shared_ptr> create_std__shared_ptr_Promise_std__string__() noexcept { + return Promise::create(); + } + inline PromiseHolder wrap_std__shared_ptr_Promise_std__string__(std::shared_ptr> promise) noexcept { + return PromiseHolder(std::move(promise)); + } + + // pragma MARK: std::function + /** + * Specialized version of `std::function`. + */ + using Func_void_std__string = std::function; + /** + * Wrapper class for a `std::function`, this can be used from Swift. + */ + class Func_void_std__string_Wrapper final { + public: + explicit Func_void_std__string_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + inline void call(std::string result) const noexcept { + _function->operator()(result); + } + private: + std::unique_ptr> _function; + } SWIFT_NONCOPYABLE; + Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_std__string_Wrapper wrap_Func_void_std__string(Func_void_std__string value) noexcept { + return Func_void_std__string_Wrapper(std::move(value)); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. @@ -280,40 +314,6 @@ namespace margelo::nitro::image::bridge::swift { return Func_void_Wrapper(std::move(value)); } - // pragma MARK: std::shared_ptr> - /** - * Specialized version of `std::shared_ptr>`. - */ - using std__shared_ptr_Promise_std__string__ = std::shared_ptr>; - inline std::shared_ptr> create_std__shared_ptr_Promise_std__string__() noexcept { - return Promise::create(); - } - inline PromiseHolder wrap_std__shared_ptr_Promise_std__string__(std::shared_ptr> promise) noexcept { - return PromiseHolder(std::move(promise)); - } - - // pragma MARK: std::function - /** - * Specialized version of `std::function`. - */ - using Func_void_std__string = std::function; - /** - * Wrapper class for a `std::function`, this can be used from Swift. - */ - class Func_void_std__string_Wrapper final { - public: - explicit Func_void_std__string_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} - inline void call(std::string result) const noexcept { - _function->operator()(result); - } - private: - std::unique_ptr> _function; - } SWIFT_NONCOPYABLE; - Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept; - inline Func_void_std__string_Wrapper wrap_Func_void_std__string(Func_void_std__string value) noexcept { - return Func_void_std__string_Wrapper(std::move(value)); - } - // pragma MARK: std::shared_ptr>> /** * Specialized version of `std::shared_ptr>>`. @@ -384,6 +384,15 @@ namespace margelo::nitro::image::bridge::swift { return Result>>::withError(error); } + // pragma MARK: Result>> + using Result_std__shared_ptr_Promise_std__string___ = Result>>; + inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::shared_ptr>& value) noexcept { + return Result>>::withValue(value); + } + inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::exception_ptr& error) noexcept { + return Result>>::withError(error); + } + // pragma MARK: Result> using Result_std__shared_ptr_HybridImageSpec__ = Result>; inline Result_std__shared_ptr_HybridImageSpec__ create_Result_std__shared_ptr_HybridImageSpec__(const std::shared_ptr& value) noexcept { @@ -411,15 +420,6 @@ namespace margelo::nitro::image::bridge::swift { return Result>>::withError(error); } - // pragma MARK: Result>> - using Result_std__shared_ptr_Promise_std__string___ = Result>>; - inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::shared_ptr>& value) noexcept { - return Result>>::withValue(value); - } - inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::exception_ptr& error) noexcept { - return Result>>::withError(error); - } - // pragma MARK: Result> using Result_std__shared_ptr_ArrayBuffer__ = Result>; inline Result_std__shared_ptr_ArrayBuffer__ create_Result_std__shared_ptr_ArrayBuffer__(const std::shared_ptr& value) noexcept { diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageSpecSwift.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageSpecSwift.hpp index 665ca254..4f767215 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageSpecSwift.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageSpecSwift.hpp @@ -33,9 +33,9 @@ namespace margelo::nitro::image { class HybridImageSpec; } #include #include "EncodedImageData.hpp" #include "ImageFormat.hpp" +#include #include #include "HybridImageSpec.hpp" -#include #include "NitroImage-Swift-Cxx-Umbrella.hpp" @@ -124,6 +124,14 @@ namespace margelo::nitro::image { auto __value = std::move(__result.value()); return __value; } + inline std::shared_ptr> toBase64Async(ImageFormat format, std::optional quality) override { + auto __result = _swiftPart.toBase64Async(static_cast(format), quality); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } inline std::shared_ptr resize(double width, double height) override { auto __result = _swiftPart.resize(std::forward(width), std::forward(height)); if (__result.hasError()) [[unlikely]] { diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec.swift b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec.swift index 68c06f12..adc8418c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec.swift +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec.swift @@ -18,6 +18,7 @@ public protocol HybridImageSpec_protocol: HybridObject { func toRawPixelDataAsync(allowGpu: Bool?) throws -> Promise func toEncodedImageData(format: ImageFormat, quality: Double?) throws -> EncodedImageData func toEncodedImageDataAsync(format: ImageFormat, quality: Double?) throws -> Promise + func toBase64Async(format: ImageFormat, quality: Double?) throws -> Promise func resize(width: Double, height: Double) throws -> (any HybridImageSpec) func resizeAsync(width: Double, height: Double) throws -> Promise<(any HybridImageSpec)> func rotate(degrees: Double, allowFastFlagRotation: Bool?) throws -> (any HybridImageSpec) diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec_cxx.swift b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec_cxx.swift index 363ce376..5cde0156 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec_cxx.swift +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageSpec_cxx.swift @@ -226,6 +226,32 @@ open class HybridImageSpec_cxx { } } + @inline(__always) + public final func toBase64Async(format: Int32, quality: bridge.std__optional_double_) -> bridge.Result_std__shared_ptr_Promise_std__string___ { + do { + let __result = try self.__implementation.toBase64Async(format: margelo.nitro.image.ImageFormat(rawValue: format)!, quality: { () -> Double? in + if bridge.has_value_std__optional_double_(quality) { + let __unwrapped = bridge.get_std__optional_double_(quality) + return __unwrapped + } else { + return nil + } + }()) + let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__string__ in + let __promise = bridge.create_std__shared_ptr_Promise_std__string__() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__string__(__promise) + __result + .then({ __result in __promiseHolder.resolve(std.string(__result)) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }() + return bridge.create_Result_std__shared_ptr_Promise_std__string___(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_std__shared_ptr_Promise_std__string___(__exceptionPtr) + } + } + @inline(__always) public final func resize(width: Double, height: Double) -> bridge.Result_std__shared_ptr_HybridImageSpec__ { do { diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.cpp index 8a8f95e6..76486dad 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.cpp @@ -20,6 +20,7 @@ namespace margelo::nitro::image { prototype.registerHybridMethod("toRawPixelDataAsync", &HybridImageSpec::toRawPixelDataAsync); prototype.registerHybridMethod("toEncodedImageData", &HybridImageSpec::toEncodedImageData); prototype.registerHybridMethod("toEncodedImageDataAsync", &HybridImageSpec::toEncodedImageDataAsync); + prototype.registerHybridMethod("toBase64Async", &HybridImageSpec::toBase64Async); prototype.registerHybridMethod("resize", &HybridImageSpec::resize); prototype.registerHybridMethod("resizeAsync", &HybridImageSpec::resizeAsync); prototype.registerHybridMethod("rotate", &HybridImageSpec::rotate); diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.hpp index 954f77d6..810a76fa 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.hpp @@ -27,9 +27,9 @@ namespace margelo::nitro::image { class HybridImageSpec; } #include #include "EncodedImageData.hpp" #include "ImageFormat.hpp" +#include #include #include "HybridImageSpec.hpp" -#include #include namespace margelo::nitro::image { @@ -68,6 +68,7 @@ namespace margelo::nitro::image { virtual std::shared_ptr> toRawPixelDataAsync(std::optional allowGpu) = 0; virtual EncodedImageData toEncodedImageData(ImageFormat format, std::optional quality) = 0; virtual std::shared_ptr> toEncodedImageDataAsync(ImageFormat format, std::optional quality) = 0; + virtual std::shared_ptr> toBase64Async(ImageFormat format, std::optional quality) = 0; virtual std::shared_ptr resize(double width, double height) = 0; virtual std::shared_ptr>> resizeAsync(double width, double height) = 0; virtual std::shared_ptr rotate(double degrees, std::optional allowFastFlagRotation) = 0; diff --git a/packages/react-native-nitro-image/src/specs/Image.nitro.ts b/packages/react-native-nitro-image/src/specs/Image.nitro.ts index b61aef15..8bf4fa96 100644 --- a/packages/react-native-nitro-image/src/specs/Image.nitro.ts +++ b/packages/react-native-nitro-image/src/specs/Image.nitro.ts @@ -113,6 +113,21 @@ export interface Image quality?: number, ): Promise + /** + * Encodes this Image in the requested {@linkcode ImageFormat} and returns the encoded bytes as a Base64 string. + * @note This method re-encodes the current Image. It does not return the original file bytes or preserve all original metadata. + * @note The returned string uses standard padded Base64 without line breaks or a data URL prefix. + * @param format The {@linkcode ImageFormat} to use for encoding this Image. + * @param quality The target Image quality from `0` (worst quality/highest compression) to `100` (best quality/least compression), rounded to the nearest integer. Defaults to `100`. {@linkcode quality} is ignored for non-compressible {@linkcode ImageFormat}s, such as {@linkcode ImageFormat | 'png'}. + * @throws If {@linkcode quality} is outside the `0...100` range. + * @example + * ```ts + * const base64 = await image.toBase64Async('jpg', 80) + * const dataUrl = `data:image/jpeg;base64,${base64}` + * ``` + */ + toBase64Async(format: ImageFormat, quality?: number): Promise + /** * Resizes this Image into a new image with the new given {@linkcode width} and {@linkcode height}. * @example