expo-pixel-perfect
Version:
Nearest-Neighbor scaling for React Native and Expo
282 lines (218 loc) • 9.99 kB
Markdown
Perfect pixel-art scaling for your Expo apps. No blur, no artifacts - just crisp, clean pixels.
<image src="screenshot.png" alt="Demo showing 16x16 pixel art scaled up with and without expo-pixel-perfect" width="200">
- Crisp nearest-neighbor scaling (no blurry pixels!)
- Advanced fractional scaling for non-integer scale factors
- Works with local, remote, and base64 images
- Platform-specific rendering modes (software/hardware) for quality vs performance
- Native performance
- Loading states with customizable components
- Error handling with fallback options
- Type-safe with full TypeScript support
- Zero configuration needed
## 🛠️ Supported Platforms
| Platform | Supported |
|----------|----------------------|
| iOS | ✅ New Architecture |
| Android | ✅ New Architecture |
| Web | ❌ |
> **Note**: This module requires the New Architecture (Fabric) to be enabled in your Expo project. It will not work with the old architecture.
> **Web Platform**: This module uses native implementations for pixel-perfect scaling and does not support web platforms. For web-specific pixel art needs, consider using CSS solutions or a web-specific library.
## 📦 Installation
```bash
npx expo install expo-pixel-perfect
```
## 🚀 Usage
### Basic Usage
```tsx
import ExpoPixelPerfectView from 'expo-pixel-perfect';
export default function Game() {
return (
<ExpoPixelPerfectView
source={require('./assets/sprite.png')}
scale={4}
/>
);
}
```
```tsx
import ExpoPixelPerfectView from 'expo-pixel-perfect';
export default function Game() {
return (
<ExpoPixelPerfectView
// Source image
source={require('./assets/sprite.png')}
// Scale to specific width
scale={{ targetWidth: 64 }}
// Use optimized algorithm for non-integer scaling
scaleMode="fractional-optimized"
// Platform-specific rendering modes
android_renderMode="software" // Higher quality on Android
ios_renderMode="software" // Higher quality on iOS
// Custom loading component
loadingComponent={<CustomLoader />}
// Error handling
fallback={<Text>Failed to load sprite</Text>}
onError={(error) => console.error('Failed to load:', error)}
// Load callback
onLoad={() => console.log('Sprite loaded successfully')}
// Standard React Native styles
style={styles.sprite}
/>
);
}
```
```tsx
import ExpoPixelPerfectView from 'expo-pixel-perfect';
export default function Game() {
// Base64 encoded pixel art
const pixelArtBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...';
return (
<ExpoPixelPerfectView
source={{
base64: pixelArtBase64,
width: 16, // Original width is required
height: 16 // Original height is required
}}
scale={3}
/>
);
}
```
```tsx
import ExpoPixelPerfectView from 'expo-pixel-perfect';
import { useWindowDimensions } from 'react-native';
export default function ResponsiveGameElement() {
const { width } = useWindowDimensions();
const spriteOriginalWidth = 32;
// Calculate a non-integer scale factor to fit screen width
const scaleFactor = width / spriteOriginalWidth / 2; // Fit 2 sprites side by side
return (
<ExpoPixelPerfectView
source={require('./assets/game-element.png')}
scale={scaleFactor}
scaleMode="fractional-optimized" // Use optimized scaling for non-integer scales
/>
);
}
```
| Prop | Type | Description |
|----------|-------------------------------------------|---------------------------------|
| source | number \| { uri: string } \| { base64: string, width: number, height: number } | Image source (local, remote, or base64) |
### Optional Props
| Prop | Type | Default | Description |
|------------------|------------------------------------------------|-------------------|-------------------------------------------|
| scale | number \| { targetWidth: number } \| { targetHeight: number } | 1 | Scaling factor or target dimensions |
| style | ViewStyle | undefined | Standard React Native view styles |
| loadingComponent | ReactNode | null | Component shown during loading |
| fallback | ReactNode | null | Component shown on error |
| onError | (error: Error) => void | undefined | Error callback |
| onLoad | () => void | undefined | Success callback |
| scaleMode | "nearest" \| "fractional-optimized" | "nearest" | Algorithm used for scaling |
| android_renderMode | "software" \| "hardware" | "hardware" | Android-specific rendering mode |
| ios_renderMode | "software" \| "hardware" | "hardware" | iOS-specific rendering mode |
This module provides platform-specific rendering modes to balance between visual quality and performance:
- **software**: Uses CPU-based rendering (View.LAYER_TYPE_SOFTWARE) for highest quality pixel-perfect rendering. Best for static UI elements or when visual quality is critical.
- **hardware**: Uses GPU-accelerated rendering (View.LAYER_TYPE_HARDWARE) for better performance. May have slightly less precise scaling but offers better battery life and performance.
### iOS Render Modes
- **software**: Uses CoreGraphics (CPU-based) rendering with precise nearest-neighbor scaling. Best for static UI elements or when visual quality is critical.
- **hardware**: Uses Core Image (GPU-accelerated) rendering for better performance. May have slightly less precise scaling but offers better battery life and performance.
```tsx
// Example of platform-specific optimization
<PixelImage
source={require('./assets/character.png')}
scale={4}
// Use software rendering on Android for best quality
android_renderMode="software"
// Use hardware acceleration on iOS for better performance
ios_renderMode="hardware"
/>
```
This module offers two different scaling algorithms to handle different types of pixel art scaling:
- Default scaling mode
- Best for integer scaling (2x, 3x, 4x, etc.)
- Fast and efficient
- May appear slightly blurry with non-integer scaling factors
### Fractional-Optimized (`scaleMode="fractional-optimized"`)
- Advanced scaling algorithm designed for non-integer scaling factors
- Uses a multi-step scaling process for sharper results
- Perfect for when you need to scale by factors like 1.5x, 2.3x, etc.
- Especially useful for responsive UIs or fitting to screen dimensions
- Uses more resources but produces significantly sharper results for fractional scales
```tsx
// Responsive UI with sharp pixel art
<PixelImage
source={require('./assets/ui-element.png')}
scale={deviceWidth / spriteWidth} // Non-integer scale to fit screen
scaleMode="fractional-optimized"
/>
```
- Start with small source images (8x8, 16x16, 32x32)
- Use PNG format for transparency support
- For fixed sizes, use integer scales (2x, 3x) with "nearest" mode for best performance
- For responsive UI, use "fractional-optimized" mode with calculated scale factors
- Provide fallback components for better user experience
- Handle loading and error states for smoother UX
- Use "software" rendering mode for static UI elements for best quality
- Use "hardware" rendering mode for animated elements for better performance
- When using base64 images, always specify the original width and height, if you can't you could render it using a native image and getting its layout
## 🤔 Common Issues
### Image appears blurry
Make sure you're using the correct scale factor. For a 16x16 image to display at 64x64, use either:
```tsx
scale={4}
// or
scale={{ targetWidth: 64 }}
```
For non-integer scaling that still looks crisp, use the fractional-optimized mode:
```tsx
scale={3.2} // Non-integer scale
scaleMode="fractional-optimized"
```
If still blurry, try using the "software" rendering mode:
```tsx
android_renderMode="software"
ios_renderMode="software"
```
Check that your asset path is correct and the image exists. The onError callback can help debug:
```tsx
onError={(error) => console.error('Loading failed:', error)}
```
When using base64 images, you must specify the original width and height:
```tsx
source={{
base64: myBase64String,
width: 16, // Important! Must be exact
height: 16 // Important! Must be exact
}}
```
```tsx
// This
<PixelImage
source={require('./assets/important-button.png')}
scale={2.5}
scaleMode="fractional-optimized"
/>
// This will be slightly blurry
<PixelImage
source={require('./assets/background-element.png')}
scale={2.5}
scaleMode="nearest"
/>
```
---
Made for ⚔️ [Pixel Odyssey](pixel-odyssey.app) by Lino Iten