ImageKnife
ImageKnife has ceased active development. We recommend migrating to imageknifepro, which provides both ArkTS and Native component usage modes, offers better image loading performance, and supports customizing network download, decoding, file caching, and memory caching via interceptors using a responsibility chain-interceptor pattern.
This project is based on Glide.
Introduction
ImageKnife is an image loading and caching library designed specifically for OpenHarmony, built for efficiency, lightness, and ease of use. Key features:
- Custom memory cache strategy with configurable cache size (LRU by default)
- Disk L2 cache: downloaded images are automatically saved to disk
- Custom image obtaining/network download implementation
- Network download progress callback
- Inherits system Image capabilities:
bordersupport for borders and rounded corners - Inherits system Image capabilities:
objectFitsupport for image scaling, including Auto mode for adaptive height - Image transformations via
transformation - Concurrent request count control with request queue priority support
- Components whose lifecycle has been destroyed no longer initiate image requests
- Custom cache key
- Custom HTTP request headers
- Cache write strategy control via
writeCacheStrategy(memory-only or file-only) - Image preloading via
preLoadCache - Cache-only image loading via
onlyRetrieveFromCache - Single or multiple image transformations (e.g., blur, brightness)
- Memory downsampling optimization to reduce memory usage
Planned features:
- Custom image decoding
Note: Version 3.x has undergone major refactoring compared to 2.x, with the following main changes:
- Rendering with the Image component instead of the Canvas component
- Refactored Dispatch logic supporting concurrent request count control and queue priority
- Custom memory cache strategy and size via
initMemoryCache- Custom image obtaining/download via
optionDifferences from 2.x:
- The
drawLifeCycleAPI is not supported (manual canvas drawing)- Parameters such as
mainScaleTypeandborderare now aligned with the system Image component- GIF/WebP animation playback and control are now handled by ImageAnimator
- Anti-aliasing related parameters are not included
Installation
ohpm install @ohos/imageknife
For more information on OpenHarmony ohpm environment setup, see How to Install OpenHarmony ohpm Packages.
To use image transformation features (e.g., blur, brightness), install the GPUImage dependency:
ohpm install @ohos/gpu_transform
Or add the dependency in your project's oh-package.json5:
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
}
Constraints
Compatibility
Verified on the following versions:
- DevEco Studio 5.1.0 Release (5.1.0.849), SDK: API18 Release(5.1.0.849)
Note: This library sets
compatibleSdkVersionto API18. It cannot be installed or used on devices running below API18.
Required Permissions
When loading network images, add the following permission to the requestPermissions field in module.json5:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
Quick Start
import { ImageKnife, ImageKnifeComponent } from '@ohos/imageknife';
// Initialize file cache once, recommended in UIAbility.onWindowStageCreate
// Parameters: context, max cached file count (256), total cache size limit (256 MB)
await ImageKnife.getInstance().initFileCache(getContext(this), 256, 256 * 1024 * 1024);
@Entry
@Component
struct ImageDemo {
build() {
Column() {
// Load a network image with a placeholder and error image
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: 'https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png',
placeholderSrc: $r('app.media.loading'), // Placeholder shown while loading
errorholderSrc: $r('app.media.app_icon'), // Error image shown on failure
objectFit: ImageFit.Contain // Image scaling mode
}
}).width(300).height(300)
}
}
}
How to Use
1. Display a Local Resource Image
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r("app.media.app_icon"),
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto
}
}).width(100).height(100)
2. Display a File from Local Context Files
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: this.localFile,
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto
}
}).width(100).height(100)
3. Display a Network Image
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: "https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png",
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto
}
}).width(100).height(100)
4. Custom Image Download
Use customGetImage to provide a custom download function, replacing the default HTTP request logic.
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: "https://file.atomgit.com/uploads/user/1704857786989_8994.jpeg",
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto,
customGetImage: custom
}
}).width(100).height(100)
// Custom image acquisition; replace with your own network request logic as needed
@Concurrent
async function custom(context: Context, src: string | PixelMap | Resource): Promise<ArrayBuffer | undefined> {
console.info("ImageKnife:: custom download: " + src)
// Example: reads from a local file; replace with your own request logic
return context.resourceManager.getMediaContentSync($r("app.media.bb").id).buffer as ArrayBuffer
}
5. Listen for Network Download Progress
ImageKnifeComponent({
imageKnifeOption: {
loadSrc:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png",
progressListener:(progress:number)=>{console.info("ImageKinfe:: call back progress = " + progress)}
}
}).width(100).height(100)
6. Set Border and Rounded Corners
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r("app.media.rabbit"),
border: { radius: 50 }
}
}).width(100).height(100)
7. Image Transformations
Single transformation:
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r("app.media.rabbit"),
border: { radius: 50 },
transformation: new BlurTransformation(3) // Blur radius 3
}
}).width(100).height(100)
Multiple combined transformations:
let transformations: collections.Array<PixelMapTransformation> = new collections.Array<PixelMapTransformation>();
transformations.push(new BlurTransformation(5)); // Blur
transformations.push(new BrightnessTransformation(0.2)); // Brightness
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r('app.media.pngSample'),
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Contain,
border: { radius: { topLeft: 50, bottomRight: 50 } }, // Rounded corners
transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined
}
}).width(300).height(300)
.rotate({ angle: 90 }) // Rotate 90 degrees
.contrast(12) // Contrast filter
Circular crop:
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150 }
}
}).width(300).height(300)
Circular crop with border:
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150, color: Color.Red, width: 5 }
}
}).width(300).height(300)
Contrast filter:
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r('app.media.pngSample')
}
}).width(300).height(300)
.contrast(12)
Rotation:
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $r('app.media.pngSample')
}
}).width(300).height(300)
.rotate({ angle: 90 })
.backgroundColor(Color.Pink)
8. Listen for Image Load Success and Failure
ImageKnifeComponent({ imageKnifeOption:
{
loadSrc: $r("app.media.rabbit"),
onLoadListener:{
onLoadStart:()=>{
this.starTime = new Date().getTime()
console.info("Load start: ");
},
onLoadFailed: (err) => {
console.error("Load Failed Reason: " + err + " cost " + (new Date().getTime() - this.starTime) + " milliseconds");
},
onLoadSuccess: (data, imageData) => {
console.info("Load Successful: cost " + (new Date().getTime() - this.starTime) + " milliseconds");
return data;
},
onLoadCancel(err){
console.info(err)
}
}
})
.width(100).height(100)
9. Synchronous Image Loading (syncLoad)
syncLoad controls whether images are loaded synchronously. The default is asynchronous. For small local Resource images, set syncLoad to true for faster loading on the main thread.
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:$r("app.media.pngSample"),
placeholderSrc:$r("app.media.loading")
},syncLoad:true
})
10. Using ImageKnifeAnimatorComponent for Animated Images
ImageKnifeAnimatorComponent({
imageKnifeOption: {
loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
placeholderSrc:$r('app.media.loading'),
errorholderSrc:$r('app.media.failed')
},animatorOption:this.animatorOption
}).width(300).height(300).backgroundColor(Color.Orange).margin({top:30})
11. Retrieve Image Load Callback Data
The req parameter in each onLoadListener callback provides detailed request information (URL, component dimensions, timestamps):
ImageKnifeComponent({ imageKnifeOption: {
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Contain,
onLoadListener: {
onLoadStart: (req) => {
let startCallBackData = JSON.stringify(req?.imageKnifeData);
},
onLoadFailed: (res, req) => {
let failedBackData = res + ";" + JSON.stringify(req?.imageKnifeData);
},
onLoadSuccess: (data, imageData, req) => {
let successBackData = JSON.stringify(req?.imageKnifeData);
},
onLoadCancel: (res, req) => {
let cancelBackData = res + ";" + JSON.stringify(req?.imageKnifeData);
}
},
border: { radius: 50 },
onComplete: (event) => {
if (event && event.loadingStatus == 0) {
let render_success = JSON.stringify(Date.now())
}
}
}
}).width(100).height(100)
12. Image Downsampling
Use downsampleOf to control the downsampling strategy, balancing display quality and memory usage:
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:$r("app.media.pngSample"),
placeholderSrc:$r('app.media.loading'),
errorholderSrc:$r('app.media.failed'),
downsampleOf: DownsampleStrategy.NONE
}
}).width(300).height(300)
13. Custom RCP Network Request
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:"http//xx.xx",
customGetImage:custom
}
})
// Custom download method
@Concurrent
async function custom(context: Context, src: string | PixelMap | Resource,headers?: Record<string,Object>): Promise<ArrayBuffer | undefined> {
return new Promise((resolve,reject)=>{
if (typeof src == "string") {
let session = GetSession.session
let req = new rcp.Request(src,"GET");
session.fetch(req).then((response)=>{
if(response.statusCode == 200) {
let buffer = response.body
resolve(buffer)
} else {
reject("rcp code:"+response.statusCode)
}
}).catch((err:BusinessError)=>{
reject("error rcp src:"+src+",err:"+JSON.stringify(err))
})
}
})
}
Component Reuse
In list reuse scenarios such as LazyForEach, you must set loadSrc to an empty string in the aboutToRecycle lifecycle and use @Watch to listen for loadSrc changes to trigger a reload. Failing to do so may leave the reused component blank.
@Component
struct ReusableImageItem {
// @Watch triggers ImageKnifeComponent to reload when loadSrc changes
@State @Watch('onLoadSrcChanged') loadSrc: string = '';
onLoadSrcChanged() {
// ImageKnifeComponent automatically re-requests and displays the image on loadSrc change
}
// Clear loadSrc before reuse to prevent stale image residue
aboutToRecycle(): void {
this.loadSrc = '';
}
build() {
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: this.loadSrc,
placeholderSrc: $r('app.media.loading'),
errorholderSrc: $r('app.media.app_icon')
}
}).width(200).height(200)
}
}
Available APIs
ImageKnife Components
| Name | Parameter Type | Description |
|---|---|---|
| ImageKnifeComponent | ImageKnifeOption | Image display component |
| ImageKnifeComponentV2 | ImageKnifeOptionV2 | Image display component based on @ComponentV2 state management; key fields such as loadSrc and transformation support reactive updates |
| ImageKnifeAnimatorComponent | ImageKnifeOption, AnimatorOption | Animated image control component |
AnimatorOption Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| state | AnimationStatus | No | Animation playback state |
| iterations | number | No | Number of playback times |
| reverse | boolean | No | Whether to play in reverse |
| onStart | () => void | No | Triggered when the animation starts |
| onFinish | () => void | No | Triggered when the animation finishes or stops |
| onPause | () => void | No | Triggered when the animation pauses |
| onCancel | () => void | No | Triggered when the animation resets to its initial state |
| onRepeat | () => void | No | Triggered when the animation repeats |
ImageKnifeOption Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| loadSrc | string | PixelMap | Resource | Yes | Main image source |
| placeholderSrc | PixelMap | Resource | No | Placeholder image shown while loading |
| errorholderSrc | PixelMap | Resource | No | Error image shown on load failure |
| objectFit | ImageFit | No | Main image scaling mode |
| placeholderObjectFit | ImageFit | No | Placeholder image scaling mode |
| errorholderObjectFit | ImageFit | No | Error image scaling mode |
| writeCacheStrategy | CacheStrategyType | No | Cache write strategy |
| onlyRetrieveFromCache | boolean | No | If true, skip network and local requests; load from cache only |
| customGetImage | (context: Context, src: string | PixelMap | Resource, headers?: Record<string, Object>) => Promise<ArrayBuffer | undefined> | No | Custom image obtaining/download method |
| border | BorderOptions | No | Border and rounded corner configuration |
| priority | taskpool.Priority | No | Request loading priority |
| context | common.UIAbilityContext | No | Context; uses global context if not specified |
| progressListener | (progress: number) => void | No | Network download progress callback, range 0 to 1 |
| signature | string | No | Custom cache key supplement |
| headerOption | Array<HeaderOptions> | No | HTTP request headers for this request |
| transformation | PixelMapTransformation | No | Image transformation (single or combined) |
| drawingColorFilter | ColorFilter | drawing.ColorFilter | No | Color filter effect |
| onComplete | (event: EventImage | undefined) => void | No | Callback triggered when image rendering is complete |
| onLoadListener | onLoadStart?: (req?: ImageKnifeRequest) => void, onLoadSuccess?: (data: string | PixelMap | undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest) => void, onLoadFailed?: (err: string, req?: ImageKnifeRequest) => void, onLoadCancel?: (res: string, req?: ImageKnifeRequest) => void | No | Image load state callbacks (start/success/failure/cancel) |
| downsampleOf | DownsampleStrategy | No | Downsampling strategy |
| httpOption | HttpRequestOption | No | Network request configuration (timeout, retry, etc.) |
| pixelName | string | No | PixelMap name for distinguishing multiple PixelMap caches |
| dynamicRangeMode | DynamicRangeMode | No | Expected image dynamic range for display |
Downsampling Strategies (DownsampleStrategy)
| Value | Description |
|---|---|
| NONE | No downsampling |
| AT_MOST | No upscaling when the requested size exceeds the actual size |
| FIT_CENTER_MEMORY | Auto-adapt both dimensions, memory-first |
| FIT_CENTER_QUALITY | Auto-adapt both dimensions, quality-first |
| CENTER_INSIDE_MEMORY | Scale down by the maximum aspect ratio, memory-first |
| CENTER_INSIDE_QUALITY | Scale down by the maximum aspect ratio, quality-first |
| AT_LEAST | Adapt using the minimum aspect ratio |
ImageKnife APIs
| API | Parameters | Description |
|---|---|---|
| initMemoryCache | newMemoryCache: IMemoryCache | Sets a custom memory cache strategy (default: LRU, max 256 entries / 128 MB; cache is not released when navigating away from a page; entries are evicted automatically once the limit is reached) |
| initFileCache | context: Context, size: number, memory: number | Initializes file cache with a max file count (size) and total capacity limit (memory, in bytes) |
| reload | request: ImageKnifeRequest | Reloads the specified image request |
| preLoad | loadSrc: string | ImageKnifeOption | Preloads an image and returns an ImageKnifeRequest |
| cancel | request: ImageKnifeRequest | Cancels the specified image request |
| preLoadCache | loadSrc: string | ImageKnifeOption | Preloads an image and returns its file cache path |
| getCacheImage | loadSrc: string, cacheType?: CacheStrategy, signature?: string | Asynchronously retrieves an image from memory or file cache |
| getCacheImageSync | loadSrc: string, cacheType?: CacheStrategy, signature?: string | Synchronously retrieves an image from memory or file cache |
| addHeader | key: string, value: Object | Adds a global HTTP request header |
| setHeaderOptions | Array<HeaderOptions> | Sets the global HTTP request header list |
| deleteHeader | key: string | Deletes a global HTTP request header |
| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource, headers?: Record<string, Object>) => Promise<ArrayBuffer | undefined> | Sets a global custom image obtaining/download method |
| setEngineKeyImpl | IEngineKey | Sets the global cache key generation strategy |
| setMaxRequests | concurrency: number | Sets the maximum number of concurrent requests |
| setConnectTimeout | timeout: number | Sets the connection timeout duration (milliseconds) |
| setReadTimeout | timeout: number | Sets the read timeout duration (milliseconds) |
| putCacheImage | url: string, pixelMap: PixelMap, cacheType?: CacheStrategy, signature?: string | Writes a PixelMap to memory or disk cache |
| removeMemoryCache | url: string | ImageKnifeOption | Removes the memory cache for the specified URL |
| removeFileCache | url: string | ImageKnifeOption | Removes the disk cache for the specified URL |
| removeAllMemoryCache | - | Clears all memory cache |
| removeAllFileCache | - | Clears all disk cache |
| getCacheLimitSize | cacheType?: CacheStrategy | Returns the capacity limit of the specified cache type |
| getCurrentCacheNum | cacheType?: CacheStrategy | Returns the current number of cached images for the specified cache type |
| getCurrentCacheSize | cacheType?: CacheStrategy | Returns the current used space of the specified cache type |
| setJpegOptimizeDecoding (3.2.9+) | enable: boolean | Enables or disables optimized JPEG decoding to reduce decoded image memory usage. Disabled by default. Not applied when a transformation is set; getCacheImage may return a non-RGBA PixelMap when enabled. |
| getJpegOptimizeDecoding (3.2.9+) | - | Returns whether optimized JPEG decoding is currently enabled |
Callbacks
| Name | Parameters | Description |
|---|---|---|
| onLoadStart | req: ImageKnifeRequest | req contains image request info (URL, component dimensions) and ImageKnifeData (request start time, memory cache check time) |
| onLoadSuccess | data: string | PixelMap | undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data: successfully loaded data; imageData: image cache info; req.imageKnifeData: original size, decoded size, format, frame count, and per-stage durations |
| onLoadFailed | err: string, req?: ImageKnifeRequest | err: error description; req.imageKnifeData contains error phase, error code, network error code, and per-stage durations |
| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason: cancellation reason; req.imageKnifeData contains error phase, error code, and per-stage durations (including cancellation time) |
Graphic Transformation Types (GPUImage Dependency Required)
| Type | Description |
|---|---|
| BlurTransformation | Blurs the image |
| BrightnessTransformation | Applies a brightness filter |
| CropSquareTransformation | Crops the image to a square |
| CropTransformation | Crops the image to a custom rectangle |
| GrayScaleTransformation | Applies a grayscale filter |
| InvertTransformation | Applies an inversion filter |
| KuwaharaTransformation | Applies a Kuwahara filter (requires GPUImage) |
| MaskTransformation | Applies a mask |
| PixelationTransformation | Applies a pixelation filter (requires GPUImage) |
| SepiaTransformation | Applies a sepia filter (requires GPUImage) |
| SketchTransformation | Applies a sketch filter (requires GPUImage) |
| SwirlTransformation | Applies a swirl filter (requires GPUImage) |
| ToonTransformation | Applies a cartoon filter (requires GPUImage) |
| VignetterTransformation | Applies a vignette filter (requires GPUImage) |
About Obfuscation
For details on code obfuscation, see Code Obfuscation Guide.
To prevent the imageknife library from being obfuscated, add the following exclusion rule to obfuscation-rules.txt:
-keep
./oh_modules/@ohos/imageknife
To disable ImageKnife's internal debug logs (e.g., showPixelMap, executeJob and other frequent hilog output), call:
import { LogUtil } from '@ohos/imageknife';
LogUtil.mLogLevel = LogUtil.OFF;
Known Issues
- The
ImageFitproperty cannot be set on theImageKnifeAnimatorcomponent. ImageKnifeComponentV2does not currently support use inside@ReusableV2-decorated components (issue #677); for@ReusableV2scenarios, please migrate to imageknifepro
Directory Structure
/ImageKnife # Project root directory
├── entry # Sample code directory
├── library # ImageKnife library directory
│ ├── src/main/ets
│ │ ├── 3rd_party # MD5 hash algorithm implementation
│ │ ├── cache # Cache implementation
│ │ ├── components # Component implementation
│ │ ├── downsampling # Image downsampling
│ │ ├── key # Cache key generation
│ │ ├── loaderStrategy # Image loading strategy
│ │ ├── model # Data model
│ │ ├── parseStrategy # Image parsing strategy
│ │ ├── queue # Request queue
│ │ ├── transform # Image transformation
│ │ ├── utils # Utility classes
│ │ ├── ImageKnife.ets # Core class
│ │ ├── ImageKnifeDispatcher.ets # Request dispatcher
│ │ └── ImageKnifeLoader.ets # Image loader
│ └── index.ets # Library API export entry
├── sharedlibrary # Cross-module image reading verification module
├── gpu_transform # GPU transformation module
├── README.md # English installation and usage instructions
└── README_zh.md # Chinese installation and usage instructions
How to Contribute
If you find any issues during use, feel free to submit an Issue or a PR.
License
This project is licensed under Apache License 2.0.