Skip to content

Commit

Permalink
Added support for loading image from bundle on iOS (#1370)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrfalch authored Feb 20, 2023
1 parent 542cb75 commit 8046939
Show file tree
Hide file tree
Showing 19 changed files with 158 additions and 25 deletions.
54 changes: 36 additions & 18 deletions docs/docs/image.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
---
id: image
title: Image
title: Images
sidebar_label: Image
slug: /images
---

## Loading Images

Images are loaded using the `useImage` hook. This hook returns an `SkImage` instance which can be passed to the `Image` component.

Images can be loaded using require statements, or by passing a network URL directly. It is also possible to load images from the app bundle using named images.

```tsx twoslash
import {useImage} from "@shopify/react-native-skia";
// Loads an image from the JavaScript bundle
const image1 = useImage(require("./assets/oslo"));
// Loads an image from the network
const image2 = useImage("https://picsum.photos/200/300");
// Loads an image that was added to the Android/iOS bundle
const image3 = useImage("Logo");
```

Loading an image is an asynchronous operation, so the `useImage` hook will return null until the image is loaded. You can use this to conditionally render the `Image` component like in the [example below](#example). The hook also provides an optional error handler as a second parameter.

## Image

Images can be drawn by specifying the output rectangle and how the image should fit into that rectangle.

| Name | Type | Description |
| :----- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| image | `SkImage` | Image instance. |
| x | `number` | Left position of the destination image. |
| y | `number` | Right position of the destination image. |
| width | `number` | Width of the destination image. |
| height | `number` | Height of the destination image. |
| fit? | `Fit` | Method to make the image fit into the rectangle. Value can be `contain`, `fill`, `cover` `fitHeight`, `fitWidth`, `scaleDown`, `none` (default is `contain`). |
| Name | Type | Description |
| :----- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| image | `SkImage` | Image instance. |
| x | `number` | Left position of the destination image. |
| y | `number` | Right position of the destination image. |
| width | `number` | Width of the destination image. |
| height | `number` | Height of the destination image. |
| fit? | `Fit` | Method to make the image fit into the rectangle. Value can be `contain`, `fill`, `cover` `fitHeight`, `fitWidth`, `scaleDown`, `none` (default is `contain`). |

### Example

```tsx twoslash
import { Canvas, Image, useImage } from "@shopify/react-native-skia";

const ImageDemo = () => {
// Alternatively, you can pass an image URL directly
// for instance: const image = useImage("https://bit.ly/3fkulX5");
const image = useImage(require("../../assets/oslo.jpg"));
const image = useImage(require("./assets/oslo.jpg"));
return (
<Canvas style={{ flex: 1 }}>
{image && (
Expand Down Expand Up @@ -72,9 +90,9 @@ const ImageDemo = () => {

## Instance Methods

| Name | Description |
| :---------------- | :------------------------------------------------------------------------------------ |
| height | Returns the possibly scaled height of the image. |
| width | Returns the possibly scaled width of the image. |
| encodeToBytes | Encodes Image pixels, returning result as UInt8Array |
| encodeToBase64 | Encodes Image pixels, returning result as a base64 encoded string |
| Name | Description |
| :------------- | :---------------------------------------------------------------- |
| height | Returns the possibly scaled height of the image. |
| width | Returns the possibly scaled width of the image. |
| encodeToBytes | Encodes Image pixels, returning result as UInt8Array |
| encodeToBase64 | Encodes Image pixels, returning result as a base64 encoded string |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions example/ios/RNSkia/Images.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "1024-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "1024-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "1024.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "1024 1.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "1024 2.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "1024.jpg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
20 changes: 18 additions & 2 deletions example/src/Tests/useAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const useAssets = () => {
const [error, setError] = useState<Error | null>(null);
const errorHandler = useCallback((e: Error) => setError(e), []);
const oslo = useImage(require("./assets/oslo.jpg"), errorHandler);
const skiaLogoJpeg = useImage("skia_logo_jpeg", errorHandler);
const skiaLogoPng = useImage("skia_logo", errorHandler);
const RobotoMedium = useTypeface(
require("./assets/Roboto-Medium.ttf"),
errorHandler
Expand All @@ -24,8 +26,22 @@ export const useAssets = () => {
if (error) {
throw new Error("Failed to load assets: " + error.message);
}
if (!RobotoMedium || !oslo || !NotoColorEmoji || !NotoSansSCRegular) {
if (
!RobotoMedium ||
!oslo ||
!NotoColorEmoji ||
!NotoSansSCRegular ||
!skiaLogoJpeg ||
!skiaLogoPng
) {
return null;
}
return { RobotoMedium, NotoColorEmoji, NotoSansSCRegular, oslo };
return {
RobotoMedium,
NotoColorEmoji,
NotoSansSCRegular,
oslo,
skiaLogoJpeg,
skiaLogoPng,
};
};
20 changes: 19 additions & 1 deletion package/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,25 @@
auto loader = [=]() {
NSURL *url = [[NSURL alloc]
initWithString:[NSString stringWithUTF8String:sourceUri.c_str()]];
NSData *data = [NSData dataWithContentsOfURL:url];

NSData *data = nullptr;
auto scheme = url.scheme;
auto extension = url.pathExtension;

if (scheme == nullptr &&
(extension == nullptr || [extension isEqualToString:@""])) {
// If the extension and scheme is nil, we assume that we're trying to
// load from the embedded iOS app bundle and will try to load image
// and get data from the image directly. imageNamed will return the
// best version of the requested image:
auto image = [UIImage imageNamed:[url absoluteString]];
// We don't know the image format (png, jpg, etc) but
// UIImagePNGRepresentation will support all of them
data = UIImagePNGRepresentation(image);
} else {
// Load from metro / node
data = [NSData dataWithContentsOfURL:url];
}

auto bytes = [data bytes];
auto skData = SkData::MakeWithCopy(bytes, [data length]);
Expand Down
Binary file added package/src/__tests__/snapshots/images/bundle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions package/src/renderer/__tests__/e2e/Image.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";

import { checkImage } from "../../../__tests__/setup";
import { images, surface } from "../setup";
import { Fill, Image as SkiaImage } from "../../components";

describe("Image loading from bundles", () => {
it("should render png, jpg from bundle", async () => {
const { width } = surface;
const size = width * 0.45;
const bundlePng = images.skiaLogoPng;
const bundleJpeg = images.skiaLogoJpeg;
const image = await surface.draw(
<>
<Fill color="white" />
<SkiaImage image={bundlePng} width={size} height={size} />
<SkiaImage
image={bundleJpeg}
width={size}
height={size}
x={width / 2}
/>
</>
);
checkImage(image, "snapshots/images/bundle.png", { maxPixelDiff: 29 });
});
});
12 changes: 10 additions & 2 deletions package/src/renderer/__tests__/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ declare global {
}
export let surface: TestingSurface;
const assets = new Map<SkImage | SkFont, string>();
export let images: { oslo: SkImage };
export let images: {
oslo: SkImage;
skiaLogoPng: SkImage;
skiaLogoJpeg: SkImage;
};
export let fonts: {
RobotoMedium: SkFont;
NotoColorEmoji: SkFont;
Expand All @@ -55,12 +59,16 @@ beforeAll(async () => {
fontSize
);
const oslo = loadImage("skia/__tests__/assets/oslo.jpg");
images = { oslo };
const skiaLogoPng = loadImage("skia/__tests__/assets/skia_logo.png");
const skiaLogoJpeg = loadImage("skia/__tests__/assets/skia_logo_jpeg.jpg");
images = { oslo, skiaLogoPng, skiaLogoJpeg };
fonts = { RobotoMedium, NotoColorEmoji, NotoSansSCRegular };
assets.set(oslo, "oslo");
assets.set(RobotoMedium, "RobotoMedium");
assets.set(NotoColorEmoji, "NotoColorEmoji");
assets.set(NotoSansSCRegular, "NotoSansSCRegular");
assets.set(skiaLogoPng, "skiaLogoPng");
assets.set(skiaLogoJpeg, "skiaLogoJpeg");
});

export const wait = (ms: number) =>
Expand Down
Binary file added package/src/skia/__tests__/assets/skia_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8046939

Please sign in to comment.