feat: Image add setImagePixels API,iOS receive ArrayBuffer directly

This commit is contained in:
pengfei.zhou 2022-03-03 18:55:52 +08:00 committed by osborn
parent aa837b807a
commit 9bd4ba8722
14 changed files with 109 additions and 161 deletions

View File

@ -71,6 +71,7 @@ import pub.doric.DoricContext;
import pub.doric.async.AsyncResult; import pub.doric.async.AsyncResult;
import pub.doric.extension.bridge.DoricMethod; import pub.doric.extension.bridge.DoricMethod;
import pub.doric.extension.bridge.DoricPlugin; import pub.doric.extension.bridge.DoricPlugin;
import pub.doric.extension.bridge.DoricPromise;
import pub.doric.resource.DoricResource; import pub.doric.resource.DoricResource;
import pub.doric.shader.flex.FlexNode; import pub.doric.shader.flex.FlexNode;
import pub.doric.utils.DoricLog; import pub.doric.utils.DoricLog;
@ -527,43 +528,11 @@ public class ImageNode extends ViewNode<ImageView> {
final int width = prop.asObject().getProperty("width").asNumber().toInt(); final int width = prop.asObject().getProperty("width").asNumber().toInt();
final int height = prop.asObject().getProperty("height").asNumber().toInt(); final int height = prop.asObject().getProperty("height").asNumber().toInt();
JSValue pixelsValue = prop.asObject().getProperty("pixels"); JSValue pixelsValue = prop.asObject().getProperty("pixels");
if (pixelsValue.isArrayBuffer()) { byte[] pixels = prop.asObject().getProperty("pixels").asArrayBuffer().value();
byte[] pixels = prop.asObject().getProperty("pixels").asArrayBuffer().value(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels); bitmap.copyPixelsFromBuffer(byteBuffer);
bitmap.copyPixelsFromBuffer(byteBuffer); view.setImageBitmap(bitmap);
view.setImageBitmap(bitmap);
} else if (pixelsValue.isString()) {
String pixelsCallbackId = pixelsValue.asString().value();
callJSResponse(pixelsCallbackId).setCallback(new AsyncResult.Callback<JSDecoder>() {
@Override
public void onResult(JSDecoder result) {
try {
JSValue value = result.decode();
if (value.isArrayBuffer()) {
byte[] pixels = value.asArrayBuffer().value();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
bitmap.copyPixelsFromBuffer(byteBuffer);
view.setImageBitmap(bitmap);
}
} catch (ArchiveException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onFinish() {
}
});
}
break; break;
default: default:
super.blend(view, name, prop); super.blend(view, name, prop);
@ -641,4 +610,17 @@ public class ImageNode extends ViewNode<ImageView> {
return new JavaValue(); return new JavaValue();
} }
} }
@DoricMethod
public void setImagePixels(JSObject imagePixels, DoricPromise promise) {
final int width = imagePixels.asObject().getProperty("width").asNumber().toInt();
final int height = imagePixels.asObject().getProperty("height").asNumber().toInt();
JSValue pixelsValue = imagePixels.asObject().getProperty("pixels");
byte[] pixels = imagePixels.asObject().getProperty("pixels").asArrayBuffer().value();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
bitmap.copyPixelsFromBuffer(byteBuffer);
mView.setImageBitmap(bitmap);
promise.resolve();
}
} }

View File

@ -44,6 +44,8 @@ export class ImageProcessorDemo extends Panel {
build(root: Group): void { build(root: Group): void {
const iv = createRef<Image>(); const iv = createRef<Image>();
const imageUrl = "https://doric.pub/about/The_Parthenon_in_Athens.jpg"; const imageUrl = "https://doric.pub/about/The_Parthenon_in_Athens.jpg";
let imageInfo: { width: number; height: number };
let pixels: ArrayBuffer;
<Scroller parent={root} layoutConfig={layoutConfig().most()}> <Scroller parent={root} layoutConfig={layoutConfig().most()}>
<VLayout <VLayout
layoutConfig={layoutConfig().mostWidth().fitHeight()} layoutConfig={layoutConfig().mostWidth().fitHeight()}
@ -64,6 +66,10 @@ export class ImageProcessorDemo extends Panel {
layoutConfig={layoutConfig().justWidth().fitHeight()} layoutConfig={layoutConfig().justWidth().fitHeight()}
width={(Environment.screenWidth / 5) * 4} width={(Environment.screenWidth / 5) * 4}
imageUrl={imageUrl} imageUrl={imageUrl}
loadCallback={async () => {
imageInfo = await iv.current.getImageInfo(context);
pixels = (await iv.current.getImagePixels(context)).slice(0);
}}
/> />
<VLayout <VLayout
layoutConfig={layoutConfig().mostWidth().fitHeight()} layoutConfig={layoutConfig().mostWidth().fitHeight()}
@ -91,13 +97,8 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>(); const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>(); const containerRef = createRef<GestureContainer>();
this.addOnRenderFinishedCallback(async () => { this.addOnRenderFinishedCallback(async () => {
const imageInfo = await iv.current.getImageInfo(context);
const pixels = (await iv.current.getImagePixels(context)).slice(
0
);
const data = new Uint8Array(pixels);
async function changeAlpha(alpha: number) { async function changeAlpha(alpha: number) {
const data = new Uint8Array(pixels);
for (let i = 0; i < data.length - 4; i += 4) { for (let i = 0; i < data.length - 4; i += 4) {
data[i + 3] = alpha; data[i + 3] = alpha;
} }
@ -147,21 +148,25 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>(); const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>(); const containerRef = createRef<GestureContainer>();
this.addOnRenderFinishedCallback(async () => { this.addOnRenderFinishedCallback(async () => {
const imageInfo = await iv.current.getImageInfo(context); let data: Uint32Array | undefined = undefined;
const pixels = (await iv.current.getImagePixels(context)).slice(
0
);
const data = new Uint32Array(pixels);
for (let i = 0; i < data.length; i++) {
let { r, g, b } = pixelToRGBA(data[i]);
data[i] = Math.floor(r * 0.2989 + g * 0.587 + b * 0.114);
}
async function binarizationImage(t: number) { async function binarizationImage(t: number) {
if (!!!data) {
data = new Uint32Array(pixels);
for (let i = 0; i < data.length; i++) {
let { r, g, b } = pixelToRGBA(data[i]);
data[i] = Math.floor(r * 0.2989 + g * 0.587 + b * 0.114);
}
}
const arrayBuffer = pixels.slice(0); const arrayBuffer = pixels.slice(0);
const ret = new Uint32Array(arrayBuffer); const ret = new Uint32Array(arrayBuffer);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
ret[i] = data[i] < t ? 0xff000000 : 0xffffffff; ret[i] = data[i] < t ? 0xff000000 : 0xffffffff;
} }
iv.current.setImagePixels(context, {
width: imageInfo.width,
height: imageInfo.height,
pixels: arrayBuffer,
});
iv.current.imagePixels = { iv.current.imagePixels = {
width: imageInfo.width, width: imageInfo.width,
height: imageInfo.height, height: imageInfo.height,
@ -208,13 +213,9 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>(); const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>(); const containerRef = createRef<GestureContainer>();
this.addOnRenderFinishedCallback(async () => { this.addOnRenderFinishedCallback(async () => {
const imageInfo = await iv.current.getImageInfo(context);
const rawPixels = (
await iv.current.getImagePixels(context)
).slice(0);
async function blurEffect(radius: number) { async function blurEffect(radius: number) {
radius = Math.round(radius); radius = Math.round(radius);
const buffer = rawPixels.slice(0); const buffer = pixels.slice(0);
const data = new Uint32Array(buffer); const data = new Uint32Array(buffer);
fastBlur(data, imageInfo.width, imageInfo.height, radius); fastBlur(data, imageInfo.width, imageInfo.height, radius);
iv.current.imagePixels = { iv.current.imagePixels = {

View File

@ -6,9 +6,5 @@
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
@interface JSValue (Doric) @interface JSValue (Doric)
- (BOOL)isArrayBuffer;
- (NSData *)toArrayBuffer;
- (id)toObjectWithArrayBuffer; - (id)toObjectWithArrayBuffer;
@end @end

View File

@ -159,29 +159,6 @@ id valueToObject(JSContext *context, JSValueRef value) {
return containerValueToObject([context JSGlobalContextRef], result); return containerValueToObject([context JSGlobalContextRef], result);
} }
- (BOOL)isArrayBuffer {
JSContextRef ctx = self.context.JSGlobalContextRef;
JSValueRef jsValueRef = self.JSValueRef;
if (self.isObject) {
JSTypedArrayType type = JSValueGetTypedArrayType(ctx, jsValueRef, NULL);
return type == kJSTypedArrayTypeArrayBuffer;
}
return NO;
}
- (NSData *)toArrayBuffer {
if (!self.isArrayBuffer) {
return nil;
}
JSContextRef ctx = self.context.JSGlobalContextRef;
JSValueRef jsValueRef = self.JSValueRef;
JSObjectRef ref = JSValueToObject(ctx, jsValueRef, NULL);
size_t size = JSObjectGetArrayBufferByteLength(ctx, ref, NULL);
void *ptr = JSObjectGetArrayBufferBytesPtr(ctx, ref, NULL);
return [[NSData alloc] initWithBytesNoCopy:ptr length:size freeWhenDone:NO];
}
- (id)toObjectWithArrayBuffer { - (id)toObjectWithArrayBuffer {
return valueToObject(self.context, self.JSValueRef); return valueToObject(self.context, self.JSValueRef);
} }

View File

@ -54,6 +54,7 @@ - (void)displayLayer:(CALayer *)layer {
#elif DORIC_USE_SDWEBIMAGE #elif DORIC_USE_SDWEBIMAGE
#import <SDWebImage/SDWebImage.h> #import <SDWebImage/SDWebImage.h>
#import <DoricPromise.h>
@interface DoricImageView : SDAnimatedImageView @interface DoricImageView : SDAnimatedImageView
@end @end
@ -582,29 +583,21 @@ - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id
NSDictionary *imagePixels = prop; NSDictionary *imagePixels = prop;
NSUInteger width = [imagePixels[@"width"] unsignedIntValue]; NSUInteger width = [imagePixels[@"width"] unsignedIntValue];
NSUInteger height = [imagePixels[@"height"] unsignedIntValue]; NSUInteger height = [imagePixels[@"height"] unsignedIntValue];
NSString *pixelsCallbackId = imagePixels[@"pixels"]; NSData *pixels = imagePixels[@"pixels"];
[[self callJSResponse:pixelsCallbackId, nil] setResultCallback:^(JSValue *pixelsValue) { CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (![pixelsValue isArrayBuffer]) { CGContextRef context = CGBitmapContextCreate((void *) pixels.bytes,
return; width,
} height,
NSData *data = [pixelsValue toArrayBuffer]; 8,
[self.doricContext.driver ensureSyncInMainQueue:^{ width * 4,
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); colorSpace,
CGContextRef context = CGBitmapContextCreate((void *) data.bytes, kCGImageAlphaPremultipliedLast);
width, CGImageRef imageRef = CGBitmapContextCreateImage(context);
height, UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:UIScreen.mainScreen.scale orientation:UIImageOrientationUp];
8, CGImageRelease(imageRef);
width * 4, CGContextRelease(context);
colorSpace, CGColorSpaceRelease(colorSpace);
kCGImageAlphaPremultipliedLast); view.image = image;
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:UIScreen.mainScreen.scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
self.view.image = image;
}];
}];
} else { } else {
[super blendView:view forPropName:name propValue:prop]; [super blendView:view forPropName:name propValue:prop];
} }
@ -741,6 +734,9 @@ - (NSDictionary *)getImageInfo {
} }
- (NSData *)getImagePixels { - (NSData *)getImagePixels {
if (!self.view.image) {
return nil;
}
CGImageRef imageRef = [self.view.image CGImage]; CGImageRef imageRef = [self.view.image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t width = CGImageGetWidth(imageRef); size_t width = CGImageGetWidth(imageRef);
@ -760,10 +756,30 @@ - (NSData *)getImagePixels {
CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef); CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
CGColorSpaceRelease(colorSpace); CGColorSpaceRelease(colorSpace);
CGContextRelease(contextRef); CGContextRelease(contextRef);
return [[NSData alloc] initWithBytesNoCopy:imageData length:width * height * bytesPerPixel freeWhenDone:YES]; return [[NSData alloc] initWithBytesNoCopy:imageData length:width * height * bytesPerPixel freeWhenDone:YES];
} }
- (void)setImagePixels:(NSDictionary *)imagePixels withPromise:(DoricPromise *)promise {
NSUInteger width = [imagePixels[@"width"] unsignedIntValue];
NSUInteger height = [imagePixels[@"height"] unsignedIntValue];
NSData *pixels = imagePixels[@"pixels"];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate((void *) pixels.bytes,
width,
height,
8,
width * 4,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:UIScreen.mainScreen.scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
self.view.image = image;
[promise resolve:nil];
}
- (void)dealloc { - (void)dealloc {
if (self.animationEndCallbackId) { if (self.animationEndCallbackId) {
#if DORIC_USE_YYWEBIMAGE #if DORIC_USE_YYWEBIMAGE

View File

@ -2295,14 +2295,8 @@ var Image = /** @class */ (function (_super) {
Image.prototype.getImagePixels = function (context) { Image.prototype.getImagePixels = function (context) {
return this.nativeChannel(context, "getImagePixels")(); return this.nativeChannel(context, "getImagePixels")();
}; };
Image.prototype.toModel = function () { Image.prototype.setImagePixels = function (context, image) {
var ret = _super.prototype.toModel.call(this); return this.nativeChannel(context, "setImagePixels")(image);
if (Reflect.has(ret.props, "imagePixels")) {
var imagePixels = Reflect.get(ret.props, "imagePixels");
var pixels_1 = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(function () { return pixels_1; });
}
return ret;
}; };
__decorate$b([ __decorate$b([
Property, Property,

View File

@ -1712,14 +1712,8 @@ class Image extends View {
getImagePixels(context) { getImagePixels(context) {
return this.nativeChannel(context, "getImagePixels")(); return this.nativeChannel(context, "getImagePixels")();
} }
toModel() { setImagePixels(context, image) {
const ret = super.toModel(); return this.nativeChannel(context, "setImagePixels")(image);
if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels);
}
return ret;
} }
} }
__decorate$b([ __decorate$b([

View File

@ -3240,14 +3240,8 @@ class Image extends View {
getImagePixels(context) { getImagePixels(context) {
return this.nativeChannel(context, "getImagePixels")(); return this.nativeChannel(context, "getImagePixels")();
} }
toModel() { setImagePixels(context, image) {
const ret = super.toModel(); return this.nativeChannel(context, "setImagePixels")(image);
if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels);
}
return ret;
} }
} }
__decorate$b([ __decorate$b([

8
doric-js/index.d.ts vendored
View File

@ -618,7 +618,7 @@ declare module 'doric/lib/src/widget/text' {
} }
declare module 'doric/lib/src/widget/image' { declare module 'doric/lib/src/widget/image' {
import { View, NativeViewModel } from "doric/lib/src/ui/view"; import { View } from "doric/lib/src/ui/view";
import { Color } from "doric/lib/src/util/color"; import { Color } from "doric/lib/src/util/color";
import { BridgeContext } from "doric/lib/src/runtime/global"; import { BridgeContext } from "doric/lib/src/runtime/global";
import { Resource } from "doric/lib/src/util/resource"; import { Resource } from "doric/lib/src/util/resource";
@ -715,7 +715,11 @@ declare module 'doric/lib/src/widget/image' {
mimeType: string; mimeType: string;
}>; }>;
getImagePixels(context: BridgeContext): Promise<ArrayBuffer>; getImagePixels(context: BridgeContext): Promise<ArrayBuffer>;
toModel(): NativeViewModel; setImagePixels(context: BridgeContext, image: {
width: number;
height: number;
pixels: ArrayBuffer;
}): Promise<void>;
} }
export function image(config: Partial<Image>): Image; export function image(config: Partial<Image>): Image;
} }

View File

@ -1,4 +1,4 @@
import { View, NativeViewModel } from "../ui/view"; import { View } from "../ui/view";
import { Color } from "../util/color"; import { Color } from "../util/color";
import { BridgeContext } from "../runtime/global"; import { BridgeContext } from "../runtime/global";
import { Resource } from "../util/resource"; import { Resource } from "../util/resource";
@ -95,6 +95,10 @@ export declare class Image extends View {
mimeType: string; mimeType: string;
}>; }>;
getImagePixels(context: BridgeContext): Promise<ArrayBuffer>; getImagePixels(context: BridgeContext): Promise<ArrayBuffer>;
toModel(): NativeViewModel; setImagePixels(context: BridgeContext, image: {
width: number;
height: number;
pixels: ArrayBuffer;
}): Promise<void>;
} }
export declare function image(config: Partial<Image>): Image; export declare function image(config: Partial<Image>): Image;

View File

@ -48,14 +48,8 @@ export class Image extends View {
getImagePixels(context) { getImagePixels(context) {
return this.nativeChannel(context, "getImagePixels")(); return this.nativeChannel(context, "getImagePixels")();
} }
toModel() { setImagePixels(context, image) {
const ret = super.toModel(); return this.nativeChannel(context, "setImagePixels")(image);
if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels);
}
return ret;
} }
} }
__decorate([ __decorate([

View File

@ -150,14 +150,12 @@ export class Image extends View {
return this.nativeChannel(context, "getImagePixels")() return this.nativeChannel(context, "getImagePixels")()
} }
toModel(): NativeViewModel { setImagePixels(context: BridgeContext, image: {
const ret = super.toModel() width: number,
if (Reflect.has(ret.props, "imagePixels")) { height: number,
const imagePixels = Reflect.get(ret.props, "imagePixels") pixels: ArrayBuffer
const pixels = imagePixels.pixels }): Promise<void> {
imagePixels.pixels = this.callback2Id(() => pixels) return this.nativeChannel(context, "setImagePixels",)(image)
}
return ret
} }
} }

View File

@ -3314,14 +3314,8 @@ class Image extends View {
getImagePixels(context) { getImagePixels(context) {
return this.nativeChannel(context, "getImagePixels")(); return this.nativeChannel(context, "getImagePixels")();
} }
toModel() { setImagePixels(context, image) {
const ret = super.toModel(); return this.nativeChannel(context, "setImagePixels")(image);
if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels);
}
return ret;
} }
} }
__decorate$b([ __decorate$b([

File diff suppressed because one or more lines are too long