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.extension.bridge.DoricMethod;
import pub.doric.extension.bridge.DoricPlugin;
import pub.doric.extension.bridge.DoricPromise;
import pub.doric.resource.DoricResource;
import pub.doric.shader.flex.FlexNode;
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 height = prop.asObject().getProperty("height").asNumber().toInt();
JSValue pixelsValue = prop.asObject().getProperty("pixels");
if (pixelsValue.isArrayBuffer()) {
byte[] pixels = prop.asObject().getProperty("pixels").asArrayBuffer().value();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
bitmap.copyPixelsFromBuffer(byteBuffer);
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;
default:
super.blend(view, name, prop);
@ -641,4 +610,17 @@ public class ImageNode extends ViewNode<ImageView> {
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 {
const iv = createRef<Image>();
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()}>
<VLayout
layoutConfig={layoutConfig().mostWidth().fitHeight()}
@ -64,6 +66,10 @@ export class ImageProcessorDemo extends Panel {
layoutConfig={layoutConfig().justWidth().fitHeight()}
width={(Environment.screenWidth / 5) * 4}
imageUrl={imageUrl}
loadCallback={async () => {
imageInfo = await iv.current.getImageInfo(context);
pixels = (await iv.current.getImagePixels(context)).slice(0);
}}
/>
<VLayout
layoutConfig={layoutConfig().mostWidth().fitHeight()}
@ -91,13 +97,8 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>();
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) {
const data = new Uint8Array(pixels);
for (let i = 0; i < data.length - 4; i += 4) {
data[i + 3] = alpha;
}
@ -147,21 +148,25 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>();
this.addOnRenderFinishedCallback(async () => {
const imageInfo = await iv.current.getImageInfo(context);
const pixels = (await iv.current.getImagePixels(context)).slice(
0
);
const data = new Uint32Array(pixels);
let data: Uint32Array | undefined = undefined;
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);
}
async function binarizationImage(t: number) {
}
const arrayBuffer = pixels.slice(0);
const ret = new Uint32Array(arrayBuffer);
for (let i = 0; i < data.length; i++) {
ret[i] = data[i] < t ? 0xff000000 : 0xffffffff;
}
iv.current.setImagePixels(context, {
width: imageInfo.width,
height: imageInfo.height,
pixels: arrayBuffer,
});
iv.current.imagePixels = {
width: imageInfo.width,
height: imageInfo.height,
@ -208,13 +213,9 @@ export class ImageProcessorDemo extends Panel {
const spinnerRef = createRef<Stack>();
const containerRef = createRef<GestureContainer>();
this.addOnRenderFinishedCallback(async () => {
const imageInfo = await iv.current.getImageInfo(context);
const rawPixels = (
await iv.current.getImagePixels(context)
).slice(0);
async function blurEffect(radius: number) {
radius = Math.round(radius);
const buffer = rawPixels.slice(0);
const buffer = pixels.slice(0);
const data = new Uint32Array(buffer);
fastBlur(data, imageInfo.width, imageInfo.height, radius);
iv.current.imagePixels = {

View File

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

View File

@ -159,29 +159,6 @@ id valueToObject(JSContext *context, JSValueRef value) {
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 {
return valueToObject(self.context, self.JSValueRef);
}

View File

@ -54,6 +54,7 @@ - (void)displayLayer:(CALayer *)layer {
#elif DORIC_USE_SDWEBIMAGE
#import <SDWebImage/SDWebImage.h>
#import <DoricPromise.h>
@interface DoricImageView : SDAnimatedImageView
@end
@ -582,15 +583,9 @@ - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id
NSDictionary *imagePixels = prop;
NSUInteger width = [imagePixels[@"width"] unsignedIntValue];
NSUInteger height = [imagePixels[@"height"] unsignedIntValue];
NSString *pixelsCallbackId = imagePixels[@"pixels"];
[[self callJSResponse:pixelsCallbackId, nil] setResultCallback:^(JSValue *pixelsValue) {
if (![pixelsValue isArrayBuffer]) {
return;
}
NSData *data = [pixelsValue toArrayBuffer];
[self.doricContext.driver ensureSyncInMainQueue:^{
NSData *pixels = imagePixels[@"pixels"];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate((void *) data.bytes,
CGContextRef context = CGBitmapContextCreate((void *) pixels.bytes,
width,
height,
8,
@ -602,9 +597,7 @@ - (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
self.view.image = image;
}];
}];
view.image = image;
} else {
[super blendView:view forPropName:name propValue:prop];
}
@ -741,6 +734,9 @@ - (NSDictionary *)getImageInfo {
}
- (NSData *)getImagePixels {
if (!self.view.image) {
return nil;
}
CGImageRef imageRef = [self.view.image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t width = CGImageGetWidth(imageRef);
@ -760,10 +756,30 @@ - (NSData *)getImagePixels {
CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
CGColorSpaceRelease(colorSpace);
CGContextRelease(contextRef);
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 {
if (self.animationEndCallbackId) {
#if DORIC_USE_YYWEBIMAGE

View File

@ -2295,14 +2295,8 @@ var Image = /** @class */ (function (_super) {
Image.prototype.getImagePixels = function (context) {
return this.nativeChannel(context, "getImagePixels")();
};
Image.prototype.toModel = function () {
var ret = _super.prototype.toModel.call(this);
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;
Image.prototype.setImagePixels = function (context, image) {
return this.nativeChannel(context, "setImagePixels")(image);
};
__decorate$b([
Property,

View File

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

View File

@ -3240,14 +3240,8 @@ class Image extends View {
getImagePixels(context) {
return this.nativeChannel(context, "getImagePixels")();
}
toModel() {
const ret = super.toModel();
if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels);
}
return ret;
setImagePixels(context, image) {
return this.nativeChannel(context, "setImagePixels")(image);
}
}
__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' {
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 { BridgeContext } from "doric/lib/src/runtime/global";
import { Resource } from "doric/lib/src/util/resource";
@ -715,7 +715,11 @@ declare module 'doric/lib/src/widget/image' {
mimeType: string;
}>;
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;
}

View File

@ -1,4 +1,4 @@
import { View, NativeViewModel } from "../ui/view";
import { View } from "../ui/view";
import { Color } from "../util/color";
import { BridgeContext } from "../runtime/global";
import { Resource } from "../util/resource";
@ -95,6 +95,10 @@ export declare class Image extends View {
mimeType: string;
}>;
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;

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long