Created
June 13, 2012 23:33
-
-
Save slodge/2927149 to your computer and use it in GitHub Desktop.
MonoDroid Preview stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed class Preview : SurfaceView, ISurfaceHolderCallback, Camera.IPreviewCallback | |
{ | |
ISurfaceHolder surface_holder; | |
Camera camera; | |
private QRCodeReader _reader; | |
public Preview(Context context) | |
: base(context) | |
{ | |
// Install a SurfaceHolder.Callback so we get notified when the | |
// underlying surface is created and destroyed. | |
surface_holder = Holder; | |
surface_holder.AddCallback(this); | |
surface_holder.SetType(SurfaceType.PushBuffers); | |
} | |
public void SurfaceCreated(ISurfaceHolder holder) | |
{ | |
// The Surface has been created, acquire the camera and tell it where | |
// to draw. | |
camera = Camera.Open(); | |
try | |
{ | |
camera.SetPreviewDisplay(holder); | |
camera.SetPreviewCallback(this); | |
} | |
catch (Exception) | |
{ | |
camera.Release(); | |
camera = null; | |
// TODO: add more exception handling logic here | |
} | |
} | |
public void SurfaceDestroyed(ISurfaceHolder holder) | |
{ | |
// Surface will be destroyed when we return, so stop the preview. | |
// Because the CameraDevice object is not a shared resource, it's very | |
// important to release it when the activity is paused. | |
camera.StopPreview(); | |
camera.Release(); | |
camera = null; | |
} | |
private Camera.Size GetOptimalPreviewSize(IList<Camera.Size> sizes, int w, int h) | |
{ | |
const double ASPECT_TOLERANCE = 0.05; | |
double targetRatio = (double)w / h; | |
if (sizes == null) | |
return null; | |
Camera.Size optimalSize = null; | |
double minDiff = Double.MaxValue; | |
int targetHeight = h; | |
// Try to find an size match aspect ratio and size | |
for (int i = 0; i < sizes.Count; i++) | |
{ | |
Camera.Size size = sizes[i]; | |
double ratio = (double)size.Width / size.Height; | |
if (Math.Abs(ratio - targetRatio) > ASPECT_TOLERANCE) | |
continue; | |
if (Math.Abs(size.Height - targetHeight) < minDiff) | |
{ | |
optimalSize = size; | |
minDiff = Math.Abs(size.Height - targetHeight); | |
} | |
} | |
// Cannot find the one match the aspect ratio, ignore the requirement | |
if (optimalSize == null) | |
{ | |
minDiff = Double.MaxValue; | |
for (int i = 0; i < sizes.Count; i++) | |
{ | |
Camera.Size size = sizes[i]; | |
if (Math.Abs(size.Height - targetHeight) < minDiff) | |
{ | |
optimalSize = size; | |
minDiff = Math.Abs(size.Height - targetHeight); | |
} | |
} | |
} | |
return optimalSize; | |
} | |
public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int w, int h) | |
{ | |
// Now that the size is known, set up the camera parameters and begin | |
// the preview. | |
Camera.Parameters parameters = camera.GetParameters(); | |
IList<Camera.Size> sizes = parameters.SupportedPreviewSizes; | |
Camera.Size optimalSize = GetOptimalPreviewSize(sizes, w, h); | |
parameters.SetPreviewSize(optimalSize.Width, optimalSize.Height); | |
camera.SetDisplayOrientation(90); | |
camera.SetParameters(parameters); | |
int dataBufferSize = (int)(optimalSize.Width * optimalSize.Height * | |
(ImageFormat.GetBitsPerPixel(camera.GetParameters().PreviewFormat) / 8.0)); | |
_reader = new QRCodeReader(); | |
camera.StartPreview(); | |
} | |
private bool sentinel = false; | |
public void DoScan() | |
{ | |
if (camera != null) | |
{ | |
//sentinel = true; | |
//camera.SetOneShotPreviewCallback(this); | |
} | |
} | |
#region Implementation of IPreviewCallback | |
/* | |
private sbyte HackByteConvert(byte input) | |
{ | |
if (input < 128) | |
{ | |
return (sbyte) input; | |
} | |
else | |
{ | |
return (sbyte)(127 - input); | |
} | |
} | |
sbyte[] sdata = new sbyte[data.Length]; | |
for (int i = 0; i < data.Length; i++) | |
{ | |
sdata[i] = HackByteConvert(data[i]); | |
} | |
*/ | |
public void OnPreviewFrame(byte[] data, Camera camera) | |
{ | |
if (sentinel) | |
return; | |
sentinel = true; | |
try | |
{ | |
var size = camera.GetParameters().PreviewSize; | |
var sdata = (sbyte[]) (Array) data; | |
Trace.Info("Length w h {0} {1} {2}", data.Length, size.Width, size.Height); | |
var source = new PlanarYUVLuminanceSource(sdata, size.Width, size.Height, 0, 0, size.Width, size.Height, false); | |
var binarizer = new HybridBinarizer(source); | |
var binBitmap = new BinaryBitmap(binarizer); | |
var result = _reader.decode(binBitmap); | |
Trace.Info("A RESULT {0}", result.Text); | |
var t = result.Text; | |
//LuminanceSource source = new RGBLuminanceSource(data, size.Width, size.Height); | |
//BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |
//var result = barcodeReader.decode(bitmap); | |
} | |
catch (Exception exception) | |
{ | |
Trace.Info("Exception masked " + exception.ToLongString()); | |
} | |
finally | |
{ | |
sentinel = false; | |
} | |
} | |
#endregion | |
} | |
public class PlanarYUVLuminanceSource : LuminanceSource | |
{ | |
private readonly sbyte[] yuvData; | |
private readonly int dataWidth; | |
private readonly int dataHeight; | |
private readonly int left; | |
private readonly int top; | |
public PlanarYUVLuminanceSource(sbyte[] yuvData, | |
int dataWidth, | |
int dataHeight, | |
int left, | |
int top, | |
int width, | |
int height, | |
bool reverseHorizontal) | |
: base(width, height) | |
{ | |
if (left + width > dataWidth || top + height > dataHeight) | |
{ | |
throw new IllegalArgumentException("Crop rectangle does not fit within image data."); | |
} | |
this.yuvData = yuvData; | |
this.dataWidth = dataWidth; | |
this.dataHeight = dataHeight; | |
this.left = left; | |
this.top = top; | |
if (reverseHorizontal) | |
{ | |
this.reverseHorizontal(width, height); | |
} | |
} | |
public override sbyte[] getRow(int y, sbyte[] row) | |
{ | |
if (y < 0 || y >= Height) | |
{ | |
throw new IllegalArgumentException("Requested row is outside the image: " + y); | |
} | |
int width = Width; | |
if (row == null || row.Length < width) | |
{ | |
row = new sbyte[width]; | |
} | |
int offset = (y + top) * dataWidth + left; | |
Array.Copy(yuvData, offset, row, 0, width); | |
return row; | |
} | |
public override sbyte[] Matrix | |
{ | |
get | |
{ | |
int width = Width; | |
int height = Height; | |
// If the caller asks for the entire underlying image, save the copy and give them the | |
// original data. The docs specifically warn that result.length must be ignored. | |
if (width == dataWidth && height == dataHeight) | |
{ | |
return yuvData; | |
} | |
int area = width*height; | |
sbyte[] matrix = new sbyte[area]; | |
int inputOffset = top*dataWidth + left; | |
// If the width matches the full width of the underlying data, perform a single copy. | |
if (width == dataWidth) | |
{ | |
Array.Copy(yuvData, inputOffset, matrix, 0, area); | |
return matrix; | |
} | |
// Otherwise copy one cropped row at a time. | |
sbyte[] yuv = yuvData; | |
for (int y = 0; y < height; y++) | |
{ | |
int outputOffset = y*width; | |
Array.Copy(yuv, inputOffset, matrix, outputOffset, width); | |
inputOffset += dataWidth; | |
} | |
return matrix; | |
} | |
} | |
public override bool CropSupported | |
{ | |
get | |
{ | |
return true; | |
} | |
} | |
public override LuminanceSource crop(int left, int top, int width, int height) | |
{ | |
return new PlanarYUVLuminanceSource(yuvData, | |
dataWidth, | |
dataHeight, | |
this.left + left, | |
this.top + top, | |
width, | |
height, | |
false); | |
} | |
public Bitmap renderCroppedGreyscaleBitmap() | |
{ | |
int width = Width; | |
int height = Height; | |
int[] pixels = new int[width * height]; | |
sbyte[] yuv = yuvData; | |
int inputOffset = top * dataWidth + left; | |
for (int y = 0; y < height; y++) | |
{ | |
int outputOffset = y * width; | |
for (int x = 0; x < width; x++) | |
{ | |
int grey = yuv[inputOffset + x] & 0xff; | |
pixels[outputOffset + x] = (int)(0xFF000000 | (grey * 0x00010101)); | |
} | |
inputOffset += dataWidth; | |
} | |
Bitmap bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888); | |
bitmap.SetPixels(pixels, 0, width, 0, 0, width, height); | |
return bitmap; | |
} | |
private void reverseHorizontal(int width, int height) | |
{ | |
sbyte[] yuvData = this.yuvData; | |
for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) | |
{ | |
int middle = rowStart + width / 2; | |
for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) | |
{ | |
sbyte temp = yuvData[x1]; | |
yuvData[x1] = yuvData[x2]; | |
yuvData[x2] = temp; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment