Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .changeset/six-birds-own.md

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- name: Build gl-react packages
run: |
for d in packages/gl-react packages/gl-react-dom packages/gl-react-headless; do
for d in packages/gl-react packages/gl-react-dom packages/gl-react-headless packages/gl-react-image; do
pnpm exec babel --root-mode upward --source-maps --extensions '.ts,.tsx' -d "$d/lib" "$d/src"
done
- run: xvfb-run -s "-ac -screen 0 1280x1024x24" pnpm test
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Releases are managed by [changesets](https://github.com/changesets/changesets).
- **`packages/gl-react-native/`** — React Native standalone implementation.
- **`packages/gl-react-expo/`** — React Native via Expo GLView.
- **`packages/gl-react-headless/`** — Node.js implementation using headless-gl.
- **`packages/gl-react-image/`** — `GLImage` component implementing `resizeMode` (cover/contain/free/stretch) in OpenGL.
- **`packages/tests/`** — Shared Jest test suite using `gl-react-headless` + `react-test-renderer`.
- **`packages/cookbook/`** — Modern examples (Vite + TypeScript + Tailwind).

Expand Down
1 change: 1 addition & 0 deletions packages/cookbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@heroicons/react": "^2.2.0",
"gl-react": "workspace:^",
"gl-react-dom": "workspace:^",
"gl-react-image": "workspace:^",
"gl-shader": "^4.2.1",
"gl-texture2d": "^2.1.0",
"gl-transitions": "^1.43.0",
Expand Down
31 changes: 31 additions & 0 deletions packages/cookbook/src/examples/imageeffects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { Surface } from "gl-react-dom";
import GLImage from "gl-react-image";
import { Saturate } from "./saturation";

// GLImage renders a regular gl-react Node, so it composes like any other:
// here the cover-cropped image is the texture input of the Saturate effect.
export default function ImageEffects({
saturation = 1,
zoom = 0.6,
centerX = 0.5,
centerY = 0.5,
}: {
saturation?: number;
zoom?: number;
centerX?: number;
centerY?: number;
}) {
return (
<Surface width={480} height={300}>
<Saturate contrast={1} brightness={1} saturation={saturation}>
<GLImage
source="https://i.imgur.com/iPKTONG.jpg"
resizeMode="cover"
zoom={zoom}
center={[centerX, centerY]}
/>
</Saturate>
</Surface>
);
}
28 changes: 28 additions & 0 deletions packages/cookbook/src/examples/imageresizemodes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { Surface } from "gl-react-dom";
import GLImage from "gl-react-image";

// The surface is wider than the image ratio, so each resizeMode
// behaves differently: cover crops, contain letterboxes, stretch distorts.
export default function ImageResizeModes({
resizeMode = "cover",
zoom = 1,
centerX = 0.5,
centerY = 0.5,
}: {
resizeMode?: "cover" | "free" | "contain" | "stretch";
zoom?: number;
centerX?: number;
centerY?: number;
}) {
return (
<Surface width={480} height={300}>
<GLImage
source="https://i.imgur.com/uTP9Xfr.jpg"
resizeMode={resizeMode}
zoom={zoom}
center={[centerX, centerY]}
/>
</Surface>
);
}
36 changes: 36 additions & 0 deletions packages/cookbook/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,42 @@ export const examples: ExampleEntry[] = [
category: "Composition",
Component: lazy(() => import("./diamondanim")),
},
{
id: "imageresizemodes",
title: "Image Resize Modes",
description: "gl-react-image: cover, free, contain and stretch resizeMode implemented in OpenGL",
category: "Composition",
Component: lazy(() => import("./imageresizemodes")),
controls: {
resizeMode: {
type: "select",
label: "Resize Mode",
options: [
{ key: "cover", label: "cover" },
{ key: "free", label: "free" },
{ key: "contain", label: "contain" },
{ key: "stretch", label: "stretch" },
],
default: "cover",
},
zoom: { type: "float", label: "Zoom", min: 0.05, max: 1, step: 0.01, default: 1 },
centerX: { type: "float", label: "Center X", min: 0, max: 1, step: 0.01, default: 0.5 },
centerY: { type: "float", label: "Center Y", min: 0, max: 1, step: 0.01, default: 0.5 },
},
},
{
id: "imageeffects",
title: "Image + Effects",
description: "GLImage cover-crop composed with a color effect: a Node like any other",
category: "Composition",
Component: lazy(() => import("./imageeffects")),
controls: {
saturation: { type: "float", label: "Saturation", min: 0, max: 2, step: 0.01, default: 1 },
zoom: { type: "float", label: "Zoom", min: 0.05, max: 1, step: 0.01, default: 0.6 },
centerX: { type: "float", label: "Center X", min: 0, max: 1, step: 0.01, default: 0.5 },
centerY: { type: "float", label: "Center Y", min: 0, max: 1, step: 0.01, default: 0.5 },
},
},

// === Blur ===
{
Expand Down
1 change: 1 addition & 0 deletions packages/cookbook/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default defineConfig({
'@': resolve(__dirname, './src'),
'gl-react': resolve(__dirname, '../gl-react/src'),
'gl-react-dom': resolve(__dirname, '../gl-react-dom/src'),
'gl-react-image': resolve(__dirname, '../gl-react-image/src'),
buffer: resolve(__dirname, './src/shims/buffer.ts'),
},
},
Expand Down
19 changes: 19 additions & 0 deletions packages/gl-react-image/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
The MIT License (MIT)
Copyright (c) 2016 - 2017 <renaudeau.gaetan@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 changes: 27 additions & 0 deletions packages/gl-react-image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# gl-react-image

Universal [gl-react](https://github.com/gre/gl-react) image component that implements various `resizeMode` in OpenGL: **cover**, **contain**, **free** and **stretch**.

```jsx
import { Surface } from "gl-react-dom"; // or gl-react-expo / gl-react-native / gl-react-headless
import GLImage from "gl-react-image";

<Surface width={300} height={200}>
<GLImage source="https://i.imgur.com/uTP9Xfr.jpg" resizeMode="cover" />
</Surface>;
```

## Props

- **`source`** *(required)*: any gl-react texture input (image URL, DOM element, child Node, Bus ref, ...).
- **`resizeMode`**: one of:
- `"cover"` *(default)*: fills the area, cropping the image. `center` and `zoom` move the crop window, clamped so the image always covers the area.
- `"free"`: like `"cover"` but without edge clamping; areas outside the image are transparent.
- `"contain"`: letterboxes the image so it fits entirely in the area.
- `"stretch"`: distorts the image to the area size.
- **`center`**: `[x, y]` crop window center in image coordinates (default `[0.5, 0.5]`). Only for `"cover"` and `"free"`.
- **`zoom`**: crop window zoom level (default `1`). Only for `"cover"` and `"free"`.

Any other prop is passed through to the underlying gl-react `Node`.

Because GLImage renders a Node, it can be composed anywhere in a gl-react shader tree: use it as the texture input of an effect, or give it effects as `source`.
37 changes: 37 additions & 0 deletions packages/gl-react-image/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "gl-react-image",
"version": "6.0.0",
"license": "MIT",
"author": "Gaëtan Renaudeau <renaudeau.gaetan@gmail.com>",
"description": "Universal gl-react image that implements various resizeMode in OpenGL (cover, contain, free, stretch)",
"keywords": [
"gl-react",
"image",
"resizeMode",
"crop",
"gl",
"opengl",
"react",
"react-component"
],
"repository": {
"type": "git",
"url": "https://github.com/gre/gl-react",
"directory": "packages/gl-react-image"
},
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"src",
"lib",
"README.md",
"LICENSE"
],
"peerDependencies": {
"gl-react": "*",
"react": ">=18"
},
"devDependencies": {
"gl-react": "workspace:^"
}
}
Loading
Loading