├── README.md ├── android └── BitmapTextureRenderer.kt ├── dart └── TextureView.dart └── ios ├── ImageFunc.h ├── ImageFunc.m ├── TextureAdapter.h └── TextureAdapter.m /README.md: -------------------------------------------------------------------------------- 1 | Flutter Texture Adapter 2 | ======================= 3 | 4 | Background 5 | ------------- 6 | 7 | This repository contains support classes for [Flutter Texture Widget][], I crafted during the development of my app, [scanaction][]. 8 | 9 | [Flutter Texture Widget][] just renders an image data. 10 | I used `Texture` not only for camera preview stream rendering but also render a camera snapshot or an image from `ImagePicker`. 11 | 12 | What I needed around the rendering were: 13 | - receive image data from an image provider and pass it to `Texture` in time 14 | - store image source data for repaint events 15 | 16 | The classes work for those purposes. 17 | 18 | Status 19 | ------- 20 | 21 | At this moment I've just extracted those classes from my application. There is no example app nor document, they are to be done. 22 | 23 | [Flutter Texture Widget]: https://docs.flutter.io/flutter/widgets/Texture-class.html 24 | [scanaction]: http://bit.ly/itunes-scanaction-us 25 | -------------------------------------------------------------------------------- /android/BitmapTextureRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.reedom.flutter.textureview 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Canvas 5 | import android.graphics.Rect 6 | import android.view.Surface 7 | import io.flutter.view.TextureRegistry 8 | import org.jetbrains.anko.AnkoLogger 9 | import org.jetbrains.anko.warn 10 | import java.util.* 11 | import kotlin.concurrent.schedule 12 | 13 | open class BitmapTextureRenderer(private val surfaceTexture: TextureRegistry.SurfaceTextureEntry) { 14 | private val log = AnkoLogger(this.javaClass) 15 | 16 | private val surface = Surface(surfaceTexture.surfaceTexture()) 17 | private var disposed = false; 18 | private var renderTask: TimerTask? = null 19 | private var sizeChanged = false 20 | private var bitmap: Bitmap? = null 21 | 22 | fun dispose() { 23 | disposed = true 24 | renderTask?.cancel() 25 | } 26 | 27 | val textureID: Long 28 | get() = surfaceTexture.id() 29 | 30 | fun render(bitmap: Bitmap) { 31 | sizeChanged = (this.bitmap == null) 32 | || (this.bitmap?.width != bitmap.width 33 | || this.bitmap?.height != bitmap.height) 34 | this.bitmap = bitmap 35 | renderBitmap() 36 | } 37 | 38 | private fun renderBitmap(retry: Int = 5) { 39 | renderTask = null 40 | if (disposed || bitmap == null) return 41 | 42 | updateBuffer() 43 | val canvas = lockCanvas() 44 | if (canvas == null) { 45 | if (0 < retry) { 46 | renderTask = Timer("draw", false).schedule(1) { renderBitmap(retry - 1) } 47 | } 48 | return 49 | } 50 | 51 | kotlin.runCatching { 52 | bitmap?.let { bitmap -> 53 | val rect = Rect(0, 0, bitmap.width, bitmap.height) 54 | canvas.drawBitmap(bitmap, rect, rect, null) 55 | } 56 | surface.unlockCanvasAndPost(canvas) 57 | }.onFailure { e -> log.warn("Failed to render: $e") } 58 | } 59 | 60 | private fun updateBuffer() { 61 | kotlin.runCatching { 62 | if (sizeChanged && bitmap != null) { 63 | sizeChanged = false 64 | surfaceTexture.surfaceTexture().setDefaultBufferSize(bitmap!!.width, bitmap!!.height) 65 | } 66 | }.onFailure { e -> log.warn("Failed to setDefaultBufferSize: $e") } 67 | } 68 | 69 | private fun lockCanvas(): Canvas? { 70 | kotlin.runCatching { 71 | return if (surface.isValid) surface.lockCanvas(null) else null 72 | }.onFailure { e -> log.warn("Failed to lock canvas: $e") } 73 | return null 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dart/TextureView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// TextureValue contains the current properties for a [TextureView]. 4 | /// And [TextureViewController] holds TextureValue as of its property. 5 | class TextureValue { 6 | const TextureValue({ 7 | this.textureId, 8 | this.size, 9 | }); 10 | 11 | /// Non-null means there is a valid texture and the ID is used with a [Texture] widget 12 | /// to render the native device side's graphics. 13 | final int textureId; 14 | 15 | /// The size of the graphics context of the native device side. 16 | final Size size; 17 | 18 | /// Determines whether there is a valid texture in the native device side. 19 | bool get hasTexture => textureId != null; 20 | 21 | /// Create a new copy of this instance with some new values. 22 | TextureValue copyWith({int textureId, Size size}) { 23 | return TextureValue( 24 | textureId: textureId ?? this.textureId, 25 | size: size ?? this.size, 26 | ); 27 | } 28 | 29 | /// Create a new copy of this instance with some new values. 30 | static TextureValue get empty => const TextureValue(); 31 | } 32 | 33 | /// A interface of a TextureView controller, which controls a TextureView widget. 34 | /// 35 | /// TextureView controllers are typically stored as member variables in [State] 36 | /// objects and are reused in each [State.build]. In design, a controller 37 | /// controls a single view. 38 | /// 39 | /// A TextureView controller does: 40 | /// 41 | /// * Create a texture in the native device side. 42 | /// 43 | /// * Delete a texture in the native device side. 44 | /// 45 | /// * Order the native side to (re-)render the texture. 46 | /// This manual operation is sometimes needed. 47 | /// E.g. the app gets back from background in iOS. 48 | /// 49 | /// * Hold texture properties. 50 | abstract class TextureViewController { 51 | /// Texture properties. 52 | TextureValue get value; 53 | 54 | /// Returns texture ID. It will create a new texture in the native device side 55 | /// if it does not exist yet. 56 | Future getOrCreateTexture(); 57 | 58 | /// Order the native side to (re-)render the texture. 59 | Future render(); 60 | 61 | /// Delete a texture in the native device side. 62 | Future releaseTexture(); 63 | } 64 | 65 | /// TextureView widget builds either [Texture] or [Container] according to its controller's state. 66 | class TextureView extends StatelessWidget { 67 | const TextureView(this.controller); 68 | 69 | final T controller; 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return FutureBuilder( 74 | future: controller.getOrCreateTexture(), 75 | builder: (context, snapshot) { 76 | return controller.value.hasTexture ? Texture(textureId: controller.value.textureId) : Container(); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ios/ImageFunc.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @class UIImage; 5 | 6 | extern CVPixelBufferRef TS_cropImage(CVPixelBufferRef pixelBuffer, CGRect cropFactor); 7 | extern CVPixelBufferRef TS_copyPixelBuffer(CVPixelBufferRef pixelBuffer); 8 | extern CGImageRef TS_createCGImageFromPixelBuffer(CVPixelBufferRef pixelBuffer); 9 | extern UIImage* TS_normalizeImage(UIImage* image, CGFloat maxWidth, CGFloat maxHeight); 10 | extern CVPixelBufferRef TS_pixelBufferFromImage(CGImageRef image); 11 | -------------------------------------------------------------------------------- /ios/ImageFunc.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ImageFunc.h" 3 | 4 | static void imageDataReleaseCallback(void *releaseRefCon, const void *baseAddress) { 5 | free((void *)baseAddress); 6 | } 7 | 8 | CVPixelBufferRef TS_cropImage(CVPixelBufferRef pixelBuffer, CGRect cropFactor) { 9 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 10 | void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); 11 | size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); 12 | size_t width = CVPixelBufferGetWidth(pixelBuffer); 13 | size_t height = CVPixelBufferGetHeight(pixelBuffer); 14 | 15 | int cropX0 = (int)(width * cropFactor.origin.x); 16 | int cropY0 = (int)(height * cropFactor.origin.y); 17 | int cropWidth = (int)(width * cropFactor.size.width); 18 | int cropHeight = (int)(height * cropFactor.size.height); 19 | int outWidth = cropWidth; 20 | int outHeight = cropHeight; 21 | vImage_Buffer inBuff; 22 | inBuff.height = cropHeight; 23 | inBuff.width = cropWidth; 24 | inBuff.rowBytes = bytesPerRow; 25 | 26 | int startpos = (int)(cropY0 * bytesPerRow + 4 * cropX0); 27 | inBuff.data = baseAddress+startpos; 28 | 29 | unsigned char *outImg = (unsigned char*)malloc(4 * outWidth * outHeight); 30 | vImage_Buffer outBuff = {outImg, outHeight, outWidth, 4 * outWidth}; 31 | 32 | vImage_Error err = vImageScale_ARGB8888(&inBuff, &outBuff, NULL, 0); 33 | if (err != kvImageNoError) NSLog(@" error %ld", err); 34 | 35 | CVPixelBufferRef outPixedBuffer = NULL; 36 | CVPixelBufferCreateWithBytes(kCFAllocatorDefault, outWidth, outHeight, kCVPixelFormatType_32BGRA, outImg, outWidth * 4, imageDataReleaseCallback, NULL, NULL, &outPixedBuffer); 37 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 38 | // free((void*)outImg); no need to free the buffer 39 | 40 | return outPixedBuffer; 41 | } 42 | 43 | CVPixelBufferRef TS_copyPixelBuffer(CVPixelBufferRef pixelBuffer) { 44 | return TS_cropImage(pixelBuffer, CGRectMake(0.0, 0.0, 1.0, 1.0)); 45 | } 46 | 47 | CVPixelBufferRef TS_copyPixelBuffer2(CVPixelBufferRef pixelBuffer) { 48 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 49 | void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); 50 | size_t width = CVPixelBufferGetWidth(pixelBuffer); 51 | size_t height = CVPixelBufferGetHeight(pixelBuffer); 52 | 53 | void* data = malloc(4 * width * height); 54 | memcpy(data, baseAddress, 4 * width * height); 55 | 56 | CVPixelBufferRef outPixedBuffer = NULL; 57 | CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, data, width * 4, imageDataReleaseCallback, NULL, NULL, &outPixedBuffer); 58 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 59 | 60 | return outPixedBuffer; 61 | } 62 | 63 | CGImageRef TS_createCGImageFromPixelBuffer(CVPixelBufferRef pixelBuffer) { 64 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 65 | 66 | uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer); 67 | size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); 68 | size_t width = CVPixelBufferGetWidth(pixelBuffer); 69 | size_t height = CVPixelBufferGetHeight(pixelBuffer); 70 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 71 | 72 | CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 73 | CGImageRef newImage = CGBitmapContextCreateImage(newContext); 74 | CGContextRelease(newContext); 75 | 76 | CGColorSpaceRelease(colorSpace); 77 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 78 | /* CVBufferRelease(imageBuffer); */ // do not call this! 79 | 80 | return newImage; 81 | } 82 | 83 | // Normalize UIImage picked by UIImagePicker. 84 | // There are the following expectation for the final image: 85 | // - the image orientation must be UIImageOrientationUp 86 | // - the image size must be within maxWidth and maxHeight having same proportion of the original 87 | // - the image color space should be simplified enough as vImage functions can handle. 88 | UIImage* TS_normalizeImage(UIImage* image, CGFloat maxWidth, CGFloat maxHeight) { 89 | BOOL hasMaxWidth = 0 < maxWidth; 90 | BOOL hasMaxHeight = 0 < maxHeight; 91 | CGFloat originalWidth = image.size.width; 92 | CGFloat originalHeight = image.size.height; 93 | CGFloat width = hasMaxWidth ? MIN(maxWidth, originalWidth) : originalWidth; 94 | CGFloat height = hasMaxHeight ? MIN(maxHeight, originalHeight) : originalHeight; 95 | 96 | if ((width < originalWidth) || (height < originalHeight)) { 97 | double downscaledWidth = floor((height / originalHeight) * originalWidth); 98 | double downscaledHeight = floor((width / originalWidth) * originalHeight); 99 | 100 | if (width < height) { 101 | if (!hasMaxWidth) { 102 | width = downscaledWidth; 103 | } else { 104 | height = downscaledHeight; 105 | } 106 | } else if (height < width) { 107 | if (!hasMaxHeight) { 108 | height = downscaledHeight; 109 | } else { 110 | width = downscaledWidth; 111 | } 112 | } else { 113 | if (originalWidth < originalHeight) { 114 | width = downscaledWidth; 115 | } else if (originalHeight < originalWidth) { 116 | height = downscaledHeight; 117 | } 118 | } 119 | } 120 | 121 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); 122 | [image drawInRect:CGRectMake(0, 0, width, height)]; 123 | 124 | UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); 125 | UIGraphicsEndImageContext(); 126 | 127 | return scaledImage; 128 | } 129 | 130 | CVPixelBufferRef TS_pixelBufferFromImage(CGImageRef image) { 131 | vImage_Buffer sourceBuffer = {}; 132 | vImage_CGImageFormat format = {}; 133 | // format.bitsPerComponent = 8; 134 | // format.bitsPerPixel = 32; 135 | // format.bitmapInfo = CGImageGetBitmapInfo(image); 136 | // format.renderingIntent = kCGRenderingIntentDefault; 137 | vImage_Error vError; 138 | vError = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, image, kvImageNoFlags); 139 | if (vError != kvImageNoError) { 140 | NSLog(@"TS_pixelBufferFromImage.vImageBuffer_InitWithCGImage returns error: %zd", vError); 141 | return nil; 142 | } 143 | 144 | // CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image); 145 | // if (alphaInfo == kCGImageAlphaPremultipliedLast || 146 | // alphaInfo == kCGImageAlphaPremultipliedFirst) { 147 | // vError = vImageUnpremultiplyData_BGRA8888(&sourceBuffer, &sourceBuffer, kvImageNoFlags); 148 | // if (vError != kvImageNoError) { 149 | // NSLog(@"TS_pixelBufferFromImage.vImageUnpremultiplyData_ARGB8888 returns error: %zd", vError); 150 | // return nil; 151 | // } 152 | // } 153 | // 154 | void* data = malloc(4 * sourceBuffer.width * sourceBuffer.height); 155 | vImage_Buffer destBuffer = {data, sourceBuffer.height, sourceBuffer.width, 4 * sourceBuffer.width}; 156 | vError = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, kvImageNoFlags); 157 | free(sourceBuffer.data); 158 | if (vError != kvImageNoError) { 159 | NSLog(@"TS_pixelBufferFromImage.vImageScale_ARGB8888 returns error: %zd", vError); 160 | return nil; 161 | } 162 | 163 | // if (alphaInfo == kCGImageAlphaPremultipliedLast || 164 | // alphaInfo == kCGImageAlphaPremultipliedFirst) { 165 | // vError = vImagePremultiplyData_BGRA8888(&destBuffer, &destBuffer, kvImageNoFlags); 166 | // if (vError != kvImageNoError) { 167 | // NSLog(@"TS_pixelBufferFromImage.vImagePremultiplyData_ARGB8888 returns error: %zd", vError); 168 | // return nil; 169 | // } 170 | // } 171 | // 172 | CVPixelBufferRef pixelBuffer = NULL; 173 | CVPixelBufferCreateWithBytes(kCFAllocatorDefault, destBuffer.width, destBuffer.height, 174 | kCVPixelFormatType_32BGRA, destBuffer.data, destBuffer.width * 4, 175 | imageDataReleaseCallback, NULL, NULL, &pixelBuffer); 176 | // free((void*)destBuffer.data); no need to free the buffer 177 | return pixelBuffer; 178 | } 179 | 180 | #if 0 181 | 182 | CVPixelBufferRef TS_pixelBufferFromImage10(CGImageRef image) { 183 | CGSize frameSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image)); // Not sure why this is even necessary, using CGImageGetWidth/Height in status/context seems to work fine too 184 | 185 | CVPixelBufferRef pixelBuffer = NULL; 186 | CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameSize.width, frameSize.height, kCVPixelFormatType_32BGRA, nil, &pixelBuffer); 187 | if (status != kCVReturnSuccess) { 188 | return NULL; 189 | } 190 | 191 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 192 | void *data = CVPixelBufferGetBaseAddress(pixelBuffer); 193 | CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 194 | CGContextRef context = CGBitmapContextCreate(data, frameSize.width, frameSize.height, 8, CVPixelBufferGetBytesPerRow(pixelBuffer), rgbColorSpace, (CGBitmapInfo) kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 195 | CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); 196 | 197 | CGColorSpaceRelease(rgbColorSpace); 198 | CGContextRelease(context); 199 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 200 | 201 | return pixelBuffer; 202 | } 203 | 204 | CVPixelBufferRef TS_pixelBufferFaster(CGImageRef image) { 205 | CVPixelBufferRef pxbuffer = NULL; 206 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 207 | [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 208 | [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 209 | nil]; 210 | 211 | size_t width = CGImageGetWidth(image); 212 | size_t height = CGImageGetHeight(image); 213 | size_t bytesPerRow = CGImageGetBytesPerRow(image); 214 | 215 | CFDataRef dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(image)); 216 | GLubyte *imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider); 217 | CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, 218 | imageData, bytesPerRow, NULL, NULL, 219 | (__bridge CFDictionaryRef)options, &pxbuffer); 220 | CFRelease(dataFromImageDataProvider); 221 | // CFRetain(pxbuffer); 222 | return pxbuffer; 223 | } 224 | 225 | CVPixelBufferRef TS_pixelBufferFromCGImageWithPool(CVPixelBufferPoolRef pixelBufferPool, CGImageRef image) { 226 | CVPixelBufferRef pxbuffer = NULL; 227 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 228 | [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 229 | [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 230 | nil]; 231 | 232 | size_t width = CGImageGetWidth(image); 233 | size_t height = CGImageGetHeight(image); 234 | size_t bytesPerRow = CGImageGetBytesPerRow(image); 235 | size_t bitsPerComponent = CGImageGetBitsPerComponent(image); 236 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); 237 | void *pxdata = NULL; 238 | 239 | if (pixelBufferPool == NULL) { 240 | NSLog(@"pixelBufferPool is null!"); 241 | } 242 | 243 | CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, pixelBufferPool, &pxbuffer); 244 | if (pxbuffer == NULL) { 245 | status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, 246 | (__bridge CFDictionaryRef) options, &pxbuffer); 247 | } 248 | 249 | if (status != kCVReturnSuccess || pxbuffer == NULL) { 250 | NSLog(@"cannot create new pixel buffer"); 251 | return NULL; 252 | } 253 | 254 | CVPixelBufferLockBaseAddress(pxbuffer, 0); 255 | pxdata = CVPixelBufferGetBaseAddress(pxbuffer); 256 | 257 | #if 1 258 | CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 259 | CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 260 | bitsPerComponent, bytesPerRow, rgbColorSpace, bitmapInfo); 261 | CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)); 262 | CGContextDrawImage(context, CGRectMake(0, 0, width,height), image); 263 | CGColorSpaceRelease(rgbColorSpace); 264 | CGContextRelease(context); 265 | #else 266 | CFDataRef dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(image)); 267 | CFIndex length = CFDataGetLength(dataFromImageDataProvider); 268 | GLubyte *imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider); 269 | memcpy(pxdata,imageData,length); 270 | CFRelease(dataFromImageDataProvider); 271 | #endif 272 | 273 | return pxbuffer; 274 | } 275 | 276 | #endif 277 | -------------------------------------------------------------------------------- /ios/TextureAdapter.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface TextureAdapter : NSObject 4 | 5 | @property(readonly, nonatomic) BOOL active; 6 | @property(readonly, nonatomic) int64_t textureId; 7 | 8 | - (instancetype)initWithRegistry:(NSObject*)registry; 9 | 10 | - (void)activateTexture; 11 | - (void)deactivateTexture; 12 | 13 | - (void)setPixelBufferNoCopy:(CVPixelBufferRef)pixelBuffer; 14 | - (void)storePixelBufferNoCopy:(CVPixelBufferRef)pixelBuffer; 15 | - (void)storePixelBuffer:(CVPixelBufferRef)pixelBuffer; 16 | - (void)renderStoredPixelBuffer; 17 | - (CVPixelBufferRef)getStoredPixelBuffer; 18 | - (CGSize)getStoredImageSize; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/TextureAdapter.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "TextureAdapter.h" 3 | #import "ImageFunc.h" 4 | 5 | @implementation TextureAdapter { 6 | BOOL _active; 7 | dispatch_queue_t _dispatch_queue; 8 | NSObject* _registry; 9 | CVPixelBufferRef _Atomic _pixelBuffer; 10 | CVPixelBufferRef _pixelBufferSource; 11 | } 12 | 13 | - (instancetype)initWithRegistry:(NSObject*)registry { 14 | self = [super init]; 15 | if (self) { 16 | _registry = registry; 17 | NSString* queue_name = [NSString stringWithFormat:@"com.reedom.flutter/text_scanner/texture_renderer/queue/%lu", (unsigned long)self.hash]; 18 | _dispatch_queue = dispatch_queue_create([queue_name cStringUsingEncoding:NSUTF8StringEncoding], DISPATCH_QUEUE_CONCURRENT); 19 | } 20 | return self; 21 | } 22 | 23 | - (void)activateTexture { 24 | if (!_active) { 25 | _active = YES; 26 | _textureId = [_registry registerTexture:self]; 27 | } 28 | } 29 | 30 | - (void)deactivateTexture { 31 | if (_active) { 32 | _active = NO; 33 | [_registry unregisterTexture:_textureId]; 34 | 35 | dispatch_barrier_sync(_dispatch_queue, ^{ 36 | if (self->_pixelBufferSource) { 37 | CFRelease(self->_pixelBufferSource); 38 | self->_pixelBufferSource = nil; 39 | } 40 | }); 41 | CVPixelBufferRef old = nil; 42 | atomic_exchange(&_pixelBuffer, old); 43 | if (old != nil) { 44 | CFRelease(old); 45 | } 46 | } 47 | } 48 | 49 | - (void)setPixelBufferNoCopy:(CVPixelBufferRef)pixelBuffer { 50 | CVPixelBufferRef old = atomic_exchange(&_pixelBuffer, pixelBuffer); 51 | [_registry textureFrameAvailable:_textureId]; 52 | if (old != nil) { 53 | CFRelease(old); 54 | } 55 | } 56 | 57 | - (void)storePixelBufferNoCopy:(CVPixelBufferRef)pixelBuffer { 58 | dispatch_barrier_sync(_dispatch_queue, ^{ 59 | if (self->_pixelBufferSource) { 60 | CFRelease(self->_pixelBufferSource); 61 | } 62 | CFRetain(pixelBuffer); 63 | self->_pixelBufferSource = pixelBuffer; 64 | }); 65 | 66 | CVPixelBufferRef old = atomic_exchange(&self->_pixelBuffer, _pixelBufferSource); 67 | [_registry textureFrameAvailable:_textureId]; 68 | if (old != nil) { 69 | CFRelease(old); 70 | } 71 | } 72 | 73 | - (void)storePixelBuffer:(CVPixelBufferRef)pixelBuffer { 74 | CVPixelBufferRef newPixelBuffer = TS_copyPixelBuffer(pixelBuffer); 75 | dispatch_barrier_sync(_dispatch_queue, ^{ 76 | if (self->_pixelBufferSource) { 77 | CFRelease(self->_pixelBufferSource); 78 | } 79 | CFRetain(newPixelBuffer); 80 | self->_pixelBufferSource = newPixelBuffer; 81 | }); 82 | 83 | CVPixelBufferRef old = atomic_exchange(&_pixelBuffer, _pixelBufferSource); 84 | [_registry textureFrameAvailable:_textureId]; 85 | if (old != nil) { 86 | CFRelease(old); 87 | } 88 | } 89 | 90 | - (void)renderStoredPixelBuffer { 91 | if (!_active) return; 92 | 93 | dispatch_sync(_dispatch_queue, ^{ 94 | if (self->_pixelBufferSource) { 95 | CFRetain(self->_pixelBufferSource); 96 | atomic_store(&self->_pixelBuffer, self->_pixelBufferSource); 97 | [self->_registry textureFrameAvailable:self->_textureId]; 98 | } 99 | }); 100 | } 101 | 102 | - (CVPixelBufferRef)getStoredPixelBuffer { 103 | __block CVPixelBufferRef pixelBuffer; 104 | dispatch_sync(_dispatch_queue, ^{ 105 | pixelBuffer = self->_pixelBufferSource; 106 | }); 107 | return pixelBuffer; 108 | } 109 | 110 | - (CGSize)getStoredImageSize { 111 | __block CGSize size; 112 | dispatch_sync(_dispatch_queue, ^{ 113 | if (self->_pixelBufferSource) { 114 | size_t width = CVPixelBufferGetWidth(self->_pixelBufferSource); 115 | size_t height = CVPixelBufferGetHeight(self->_pixelBufferSource); 116 | size = CGSizeMake(width, height); 117 | } else { 118 | size = CGSizeZero; 119 | } 120 | }); 121 | return size; 122 | } 123 | 124 | #pragma mark FlutterTexture 125 | 126 | - (CVPixelBufferRef)copyPixelBuffer { 127 | CVPixelBufferRef pixelBuffer = _pixelBuffer; 128 | atomic_exchange(&_pixelBuffer, nil); 129 | return pixelBuffer; 130 | } 131 | 132 | @end 133 | --------------------------------------------------------------------------------