Skip to content
Open
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
21 changes: 21 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 103 additions & 0 deletions example/__tests__/blurhash.harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { describe, expect, it } from 'react-native-harness'
import { Images } from 'react-native-nitro-image'
import {
BlurHash,
getAverageColor,
isBlurhashValid,
} from 'react-native-nitro-image-blurhash'

const RED = { r: 1, g: 0, b: 0, a: 1 }
const BLUE = { r: 0, g: 0, b: 1, a: 1 }

describe('BlurHash - round-trip', () => {
it('encodes a small image to a BlurHash string', () => {
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
expect(typeof hash).toBe('string')
expect(hash.length).toBeGreaterThan(0)
expect(isBlurhashValid(hash)).toEqual({ isValid: true })
})

it('decode produces an Image of requested dimensions', () => {
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
const decoded = BlurHash.decode(hash, 16, 24, 1)
expect(decoded.width).toBe(16)
expect(decoded.height).toBe(24)
})

it('async variants round-trip encode + decode', async () => {
const source = Images.createBlankImage(64, 64, true, RED).resize(32, 32)
const hash = await BlurHash.encodeAsync(source, 4, 3)
const decoded = await BlurHash.decodeAsync(hash, 32, 32, 1)
expect(decoded.width).toBe(32)
expect(decoded.height).toBe(32)
})

it('throws when decoding an invalid hash', () => {
expect(() => BlurHash.decode('bad', 16, 16, 1)).toThrow()
})
})

describe('isBlurhashValid', () => {
it('returns isValid:true for a freshly produced hash', () => {
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
expect(isBlurhashValid(hash)).toEqual({ isValid: true })
})

it('rejects a malformed hash with an errorReason', () => {
const result = isBlurhashValid('bad')
expect(result.isValid).toBe(false)
if (!result.isValid) {
expect(result.errorReason.length).toBeGreaterThan(0)
}
})
})

describe('getAverageColor', () => {
it('returns r/g/b channels in [0, 1] for a valid hash', () => {
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
const color = getAverageColor(hash)
expect(color).toBeDefined()
if (color != null) {
for (const channel of [color.r, color.g, color.b]) {
expect(channel).toBeGreaterThanOrEqual(0)
expect(channel).toBeLessThanOrEqual(1)
}
}
})

it('returns undefined for a too-short hash', () => {
expect(getAverageColor('short')).toBeUndefined()
})

it('reflects the source image: blue source is blue-dominant', () => {
const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
const color = getAverageColor(hash)
expect(color).toBeDefined()
if (color != null) {
expect(color.b).toBeGreaterThan(color.r)
expect(color.b).toBeGreaterThan(color.g)
}
})

it('reflects the source image: red source is red-dominant', () => {
const source = Images.createBlankImage(64, 64, true, RED).resize(32, 32)
const hash = BlurHash.encode(source, 4, 3)
const color = getAverageColor(hash)
expect(color).toBeDefined()
if (color != null) {
expect(color.r).toBeGreaterThan(color.g)
expect(color.r).toBeGreaterThan(color.b)
}
})
})

describe('BlurHash.clearCosineCache', () => {
it('does not throw', () => {
expect(() => BlurHash.clearCosineCache()).not.toThrow()
})
})
35 changes: 32 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- NitroImageBlurhash (0.0.1):
- hermes-engine
- NitroImage
- NitroModules
- RCTRequired
- RCTTypeSafety
- React-callinvoker
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- NitroModules (0.35.9):
- hermes-engine
- RCTRequired
Expand Down Expand Up @@ -2027,6 +2052,7 @@ DEPENDENCIES:
- "HarnessUI (from `../../node_modules/@react-native-harness/ui`)"
- hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- NitroImage (from `../../node_modules/react-native-nitro-image`)
- NitroImageBlurhash (from `../../node_modules/react-native-nitro-image-blurhash`)
- NitroModules (from `../../node_modules/react-native-nitro-modules`)
- NitroWebImage (from `../../node_modules/react-native-nitro-web-image`)
- RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
Expand Down Expand Up @@ -2123,6 +2149,8 @@ EXTERNAL SOURCES:
:tag: hermes-v250829098.0.10
NitroImage:
:path: "../../node_modules/react-native-nitro-image"
NitroImageBlurhash:
:path: "../../node_modules/react-native-nitro-image-blurhash"
NitroModules:
:path: "../../node_modules/react-native-nitro-modules"
NitroWebImage:
Expand Down Expand Up @@ -2281,9 +2309,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
HarnessUI: f439596aec93ff76765451f834c93f966d54e53e
hermes-engine: ef561c35b1325a953b54456ddbd27ee0faf01ba4
hermes-engine: b0d99d8b69831141e3f5630b66c979d5541cc253
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
NitroImage: 33c4698f86b081bd3d0245223f34ea561b0934ea
NitroImageBlurhash: c12c919e9172630bf7e034887f620ff445a9ac71
NitroModules: 16bc17a076b12304d608f7c915b9d321f56dfc19
NitroWebImage: 462cc2cb709258a24565ea545524c30d135f0c5a
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
Expand All @@ -2294,7 +2323,7 @@ SPEC CHECKSUMS:
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
React-Core-prebuilt: 5babb62ae79bf6d5e6444270747410c815d25442
React-Core-prebuilt: 982bcd2eaa2ba6f3b96bb0d870b5bbd5478d21b8
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
React-debug: 92944dc4d89f56d640e75498266cbde557a48189
Expand Down Expand Up @@ -2357,7 +2386,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2
ReactCodegen: e5d55b301414d3cb48738d6630b434e59cf5cc57
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
ReactNativeDependencies: 86f567dc5edd6f71876342f497cbdc0c1edac579
ReactNativeDependencies: a1701c87c7ddc647e01b8232332236bb022f2b56
RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
Expand Down
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-harness": "^1.3.0",
"react-native-nitro-image": "workspace:*",
"react-native-nitro-image-blurhash": "workspace:*",
"react-native-nitro-modules": "0.35.9",
"react-native-nitro-web-image": "workspace:*",
"react-native-safe-area-context": "^5.8.0",
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
"workspaces": [
"packages/react-native-nitro-image",
"packages/react-native-nitro-web-image",
"packages/react-native-nitro-image-blurhash",
"example"
],
"overrides": {
"@babel/preset-env": "^7.29.7",
"@babel/plugin-transform-modules-systemjs": "^7.29.7"
},
"scripts": {
"build": "bun run --cwd packages/react-native-nitro-image build && bun run --cwd packages/react-native-nitro-web-image build",
"specs": "bun run build && bun run --cwd packages/react-native-nitro-image specs && bun run --cwd packages/react-native-nitro-web-image specs",
"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",
"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",
"bootstrap": "bun i && bun run build && cd example && bundle install && bun pods",
"typecheck": "bun --filter=\"**\" typecheck",
"lint": "biome check --write",
Expand All @@ -24,6 +25,7 @@
"release": "./scripts/release.sh",
"image": "bun --cwd packages/react-native-nitro-image",
"web-image": "bun --cwd packages/react-native-nitro-web-image",
"blurhash": "bun --cwd packages/react-native-nitro-image-blurhash",
"example": "bun --cwd example"
},
"devDependencies": {
Expand Down Expand Up @@ -67,6 +69,10 @@
"file": "packages/react-native-nitro-web-image/package.json",
"path": "version"
},
{
"file": "packages/react-native-nitro-image-blurhash/package.json",
"path": "version"
},
{
"file": "example/package.json",
"path": "version"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "NitroImageBlurhash"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 }
s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" }

s.source_files = [
# Implementation (Swift)
"ios/**/*.{swift}",
# Autolinking/Registration (Objective-C++)
"ios/**/*.{m,mm}",
# Implementation (C++ objects)
"cpp/**/*.{hpp,cpp}",
]

load 'nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb'
add_nitrogen_files(s)

s.dependency 'React-jsi'
s.dependency 'React-callinvoker'
s.dependency 'NitroImage'
install_modules_dependencies(s)
end
38 changes: 38 additions & 0 deletions packages/react-native-nitro-image-blurhash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# react-native-nitro-template

This is a template for Nitro Modules.

## Usage

Clone this repo, and change all `$$*$$` names according to your `nitro.json` file.

## Contributing

Contribute a change to this template to update it for newer React Native versions.

## Structure

- [`android/`](android): All your `android`-specific implementations.
- [`build.gradle`](android/build.gradle): The gradle build file. This contains four important pieces:
1. Standard react-native library boilerplate code
2. Configures Kotlin (`apply plugin: 'org.jetbrains.kotlin.android'`)
3. Adds all Nitrogen files (`apply from: '.../NitroImageBlurhash+autolinking.gradle'`)
4. Triggers the native C++ build (via CMake/`externalNativeBuild`)
- [`CMakeLists.txt`](android/CMakeLists.txt): The CMake build file to build C++ code. This contains four important pieces:
1. Creates a library called `NitroImageBlurhash` (same as in `nitro.json`)
2. Adds all Nitrogen files (`include(.../NitroImageBlurhash+autolinking.cmake)`)
3. Adds all custom C++ files (only `HybridTestObjectCpp.cpp`)
4. Adds a `cpp-adapter.cpp` file, which autolinks all C++ HybridObjects (only `HybridTestObjectCpp`)
- [`src/main/java/com/margelo/nitro/nitroimageblurhash/`](android/src/main/java/com/margelo/nitro/nitroimageblurhash/): All Kotlin implementations.
- [`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.
- [`cpp/`](cpp): All your cross-platform implementations. (only `HybridTestObjectCpp.cpp`)
- [`ios/`](ios): All your iOS-specific implementations.
- [`nitrogen/`](nitrogen): All files generated by nitrogen. You should commit this folder to git.
- [`src/`](src): The TypeScript codebase. This defines all HybridObjects and loads them at runtime.
- [`specs/`](src/specs): All HybridObject types. Nitrogen will run on all `*.nitro.ts` files.
- [`nitro.json`](nitro.json): The configuration file for nitrogen. This will define all native namespaces, as well as the library name.
- [`NitroImageBlurhash.podspec`](NitroImageBlurhash.podspec): The iOS podspec build file to build the iOS code. This contains three important pieces:
1. Specifies the Pod's name. This must be identical to the name specified in `nitro.json`.
2. Adds all of your `.swift` or `.cpp` files (implementations).
3. Adds all Nitrogen files (`add_nitrogen_files(s)`)
- [`package.json`](package.json): The npm package.json file. `react-native-nitro-modules` should be a `peerDependency`.
31 changes: 31 additions & 0 deletions packages/react-native-nitro-image-blurhash/android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
project(NitroImageBlurhash)
cmake_minimum_required(VERSION 3.9.0)

set (PACKAGE_NAME NitroImageBlurhash)
set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 20)

# Define C++ library and add all sources
add_library(${PACKAGE_NAME} SHARED
src/main/cpp/cpp-adapter.cpp
)

# Add Nitrogen specs :)
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake)

# Set up local includes
include_directories(
"src/main/cpp"
"../cpp"
)

find_library(LOG_LIB log)
find_package(react-native-nitro-image REQUIRED) # <-- for the HybridImage type

# Link all libraries together
target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
android # <-- Android core
react-native-nitro-image::NitroImage # <-- NitroImage
)
Loading