Skip to content

Commit 6949eaa

Browse files
committed
feat: add blurhash package
1 parent 1bbc904 commit 6949eaa

64 files changed

Lines changed: 3264 additions & 7 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/.harness/manifest.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
global.RN_HARNESS = {
2-
appRegistryComponentName: 'NitroImageExample',
3-
disableViewFlattening: false,
4-
}
1+
global.RN_HARNESS = {
2+
appRegistryComponentName: 'NitroImageExample',
3+
disableViewFlattening: false,
4+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, it } from 'react-native-harness'
2+
import { Images } from 'react-native-nitro-image'
3+
import {
4+
BlurHash,
5+
getAverageColor,
6+
isBlurhashValid,
7+
} from 'react-native-nitro-image-blurhash'
8+
9+
const RED = { r: 1, g: 0, b: 0, a: 1 }
10+
const BLUE = { r: 0, g: 0, b: 1, a: 1 }
11+
12+
describe('BlurHash - round-trip', () => {
13+
it('encodes a small image to a BlurHash string', () => {
14+
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
15+
const hash = BlurHash.encode(source, 4, 3)
16+
expect(typeof hash).toBe('string')
17+
expect(hash.length).toBeGreaterThan(0)
18+
expect(isBlurhashValid(hash)).toEqual({ isValid: true })
19+
})
20+
21+
it('decode produces an Image of requested dimensions', () => {
22+
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
23+
const hash = BlurHash.encode(source, 4, 3)
24+
const decoded = BlurHash.decode(hash, 16, 24, 1)
25+
expect(decoded.width).toBe(16)
26+
expect(decoded.height).toBe(24)
27+
})
28+
29+
it('async variants round-trip encode + decode', async () => {
30+
const source = Images.createBlankImage(64, 64, true, RED).resize(32, 32)
31+
const hash = await BlurHash.encodeAsync(source, 4, 3)
32+
const decoded = await BlurHash.decodeAsync(hash, 32, 32, 1)
33+
expect(decoded.width).toBe(32)
34+
expect(decoded.height).toBe(32)
35+
})
36+
})
37+
38+
describe('isBlurhashValid', () => {
39+
it('returns isValid:true for a freshly produced hash', () => {
40+
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
41+
const hash = BlurHash.encode(source, 4, 3)
42+
expect(isBlurhashValid(hash)).toEqual({ isValid: true })
43+
})
44+
45+
it('rejects a malformed hash with an errorReason', () => {
46+
const result = isBlurhashValid('bad')
47+
expect(result.isValid).toBe(false)
48+
if (!result.isValid) {
49+
expect(result.errorReason.length).toBeGreaterThan(0)
50+
}
51+
})
52+
})
53+
54+
describe('getAverageColor', () => {
55+
it('returns r/g/b channels in [0, 1] for a valid hash', () => {
56+
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
57+
const hash = BlurHash.encode(source, 4, 3)
58+
const color = getAverageColor(hash)
59+
expect(color).toBeDefined()
60+
if (color != null) {
61+
for (const channel of [color.r, color.g, color.b]) {
62+
expect(channel).toBeGreaterThanOrEqual(0)
63+
expect(channel).toBeLessThanOrEqual(1)
64+
}
65+
}
66+
})
67+
68+
it('returns undefined for a too-short hash', () => {
69+
expect(getAverageColor('short')).toBeUndefined()
70+
})
71+
})
72+
73+
describe('BlurHash.clearCosineCache', () => {
74+
it('does not throw', () => {
75+
expect(() => BlurHash.clearCosineCache()).not.toThrow()
76+
})
77+
})

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"react-native-fast-image": "^8.6.3",
2020
"react-native-harness": "^1.3.0",
2121
"react-native-nitro-image": "workspace:*",
22+
"react-native-nitro-image-blurhash": "workspace:*",
2223
"react-native-nitro-modules": "0.35.9",
2324
"react-native-nitro-web-image": "workspace:*",
2425
"react-native-safe-area-context": "^5.8.0",

example/rn-harness.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const config = {
1919
}),
2020
applePlatform({
2121
name: 'ios',
22-
device: appleSimulator('iPhone 17 Pro', '26.2'),
22+
device: appleSimulator('iPhone 17 Pro', '26.5'),
2323
bundleId: 'com.mrousavy.nitro.image',
2424
}),
2525
],

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
"workspaces": [
88
"packages/react-native-nitro-image",
99
"packages/react-native-nitro-web-image",
10+
"packages/react-native-nitro-image-blurhash",
1011
"example"
1112
],
1213
"overrides": {
1314
"@babel/preset-env": "^7.29.7",
1415
"@babel/plugin-transform-modules-systemjs": "^7.29.7"
1516
},
1617
"scripts": {
17-
"build": "bun run --cwd packages/react-native-nitro-image build && bun run --cwd packages/react-native-nitro-web-image build",
18-
"specs": "bun run build && bun run --cwd packages/react-native-nitro-image specs && bun run --cwd packages/react-native-nitro-web-image specs",
18+
"build": "bun run --cwd packages/react-native-nitro-image build && bun run --cwd packages/react-native-nitro-web-image build && bun run --cwd packages/react-native-nitro-image-blurhash build",
19+
"specs": "bun run build && bun run --cwd packages/react-native-nitro-image specs && bun run --cwd packages/react-native-nitro-web-image specs && bun run --cwd packages/react-native-nitro-image-blurhash specs",
1920
"bootstrap": "bun i && bun run build && cd example && bundle install && bun pods",
2021
"typecheck": "bun --filter=\"**\" typecheck",
2122
"lint": "biome check --write",
@@ -24,6 +25,7 @@
2425
"release": "./scripts/release.sh",
2526
"image": "bun --cwd packages/react-native-nitro-image",
2627
"web-image": "bun --cwd packages/react-native-nitro-web-image",
28+
"blurhash": "bun --cwd packages/react-native-nitro-image-blurhash",
2729
"example": "bun --cwd example"
2830
},
2931
"devDependencies": {
@@ -67,6 +69,10 @@
6769
"file": "packages/react-native-nitro-web-image/package.json",
6870
"path": "version"
6971
},
72+
{
73+
"file": "packages/react-native-nitro-image-blurhash/package.json",
74+
"path": "version"
75+
},
7076
{
7177
"file": "example/package.json",
7278
"path": "version"
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# OSX
2+
#
3+
.DS_Store
4+
5+
# XDE
6+
.expo/
7+
8+
# VSCode
9+
.vscode/
10+
jsconfig.json
11+
12+
# Xcode
13+
#
14+
build/
15+
*.pbxuser
16+
!default.pbxuser
17+
*.mode1v3
18+
!default.mode1v3
19+
*.mode2v3
20+
!default.mode2v3
21+
*.perspectivev3
22+
!default.perspectivev3
23+
xcuserdata
24+
*.xccheckout
25+
*.moved-aside
26+
DerivedData
27+
*.hmap
28+
*.ipa
29+
*.xcuserstate
30+
project.xcworkspace
31+
32+
# Android/IJ
33+
#
34+
.classpath
35+
.cxx
36+
.gradle
37+
.idea
38+
.project
39+
.settings
40+
local.properties
41+
android.iml
42+
43+
# Cocoapods
44+
#
45+
example/ios/Pods
46+
47+
# Ruby
48+
example/vendor/
49+
50+
# node.js
51+
#
52+
node_modules/
53+
npm-debug.log
54+
yarn-debug.log
55+
yarn-error.log
56+
57+
# BUCK
58+
buck-out/
59+
\.buckd/
60+
android/app/libs
61+
android/keystores/debug.keystore
62+
63+
# Yarn
64+
.yarn/*
65+
!.yarn/patches
66+
!.yarn/plugins
67+
!.yarn/releases
68+
!.yarn/sdks
69+
!.yarn/versions
70+
71+
# Expo
72+
.expo/
73+
74+
# Turborepo
75+
.turbo/
76+
77+
# generated by bob
78+
lib/
79+
80+
# caches
81+
.eslintcache
82+
.cache
83+
*.tsbuildinfo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require "json"
2+
3+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4+
5+
Pod::Spec.new do |s|
6+
s.name = "NitroImageBlurhash"
7+
s.version = package["version"]
8+
s.summary = package["description"]
9+
s.homepage = package["homepage"]
10+
s.license = package["license"]
11+
s.authors = package["author"]
12+
13+
s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 }
14+
s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" }
15+
16+
s.source_files = [
17+
# Implementation (Swift)
18+
"ios/**/*.{swift}",
19+
# Autolinking/Registration (Objective-C++)
20+
"ios/**/*.{m,mm}",
21+
# Implementation (C++ objects)
22+
"cpp/**/*.{hpp,cpp}",
23+
]
24+
25+
load 'nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb'
26+
add_nitrogen_files(s)
27+
28+
s.dependency 'React-jsi'
29+
s.dependency 'React-callinvoker'
30+
s.dependency 'NitroImage'
31+
install_modules_dependencies(s)
32+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# react-native-nitro-template
2+
3+
This is a template for Nitro Modules.
4+
5+
## Usage
6+
7+
Clone this repo, and change all `$$*$$` names according to your `nitro.json` file.
8+
9+
## Contributing
10+
11+
Contribute a change to this template to update it for newer React Native versions.
12+
13+
## Structure
14+
15+
- [`android/`](android): All your `android`-specific implementations.
16+
- [`build.gradle`](android/build.gradle): The gradle build file. This contains four important pieces:
17+
1. Standard react-native library boilerplate code
18+
2. Configures Kotlin (`apply plugin: 'org.jetbrains.kotlin.android'`)
19+
3. Adds all Nitrogen files (`apply from: '.../NitroImageBlurhash+autolinking.gradle'`)
20+
4. Triggers the native C++ build (via CMake/`externalNativeBuild`)
21+
- [`CMakeLists.txt`](android/CMakeLists.txt): The CMake build file to build C++ code. This contains four important pieces:
22+
1. Creates a library called `NitroImageBlurhash` (same as in `nitro.json`)
23+
2. Adds all Nitrogen files (`include(.../NitroImageBlurhash+autolinking.cmake)`)
24+
3. Adds all custom C++ files (only `HybridTestObjectCpp.cpp`)
25+
4. Adds a `cpp-adapter.cpp` file, which autolinks all C++ HybridObjects (only `HybridTestObjectCpp`)
26+
- [`src/main/java/com/margelo/nitro/nitroimageblurhash/`](android/src/main/java/com/margelo/nitro/nitroimageblurhash/): All Kotlin implementations.
27+
- [`NitroImageBlurhashPackage.kt`](android/src/main/java/com/margelo/nitro/nitroimageblurhash/NitroImageBlurhashPackage.kt): The react-native package. You need this because the react-native CLI only adds libraries if they have a `*Package.kt` file. In here, you can autolink all Kotlin HybridObjects.
28+
- [`cpp/`](cpp): All your cross-platform implementations. (only `HybridTestObjectCpp.cpp`)
29+
- [`ios/`](ios): All your iOS-specific implementations.
30+
- [`nitrogen/`](nitrogen): All files generated by nitrogen. You should commit this folder to git.
31+
- [`src/`](src): The TypeScript codebase. This defines all HybridObjects and loads them at runtime.
32+
- [`specs/`](src/specs): All HybridObject types. Nitrogen will run on all `*.nitro.ts` files.
33+
- [`nitro.json`](nitro.json): The configuration file for nitrogen. This will define all native namespaces, as well as the library name.
34+
- [`NitroImageBlurhash.podspec`](NitroImageBlurhash.podspec): The iOS podspec build file to build the iOS code. This contains three important pieces:
35+
1. Specifies the Pod's name. This must be identical to the name specified in `nitro.json`.
36+
2. Adds all of your `.swift` or `.cpp` files (implementations).
37+
3. Adds all Nitrogen files (`add_nitrogen_files(s)`)
38+
- [`package.json`](package.json): The npm package.json file. `react-native-nitro-modules` should be a `peerDependency`.

0 commit comments

Comments
 (0)