Skip to content

Commit

Permalink
PriceHax v2.1
Browse files Browse the repository at this point in the history
Feature: Option to disable dithering (faster transmit)
Feature: Use PP16 protocol (faster transmit)
Bugfix: BWR conversion fixed
Bugfix: Some particular image sizes were not accepted by ESLs
Bugfix: Cam preview resume
Better thresholds for color detection
Adjusted repeat and delay transmit parameters
Enable/disable scaling slider depending on original image size
Cleanups, removed some logging, Lambda-ized
  • Loading branch information
furrtek committed Sep 4, 2023
1 parent c228170 commit cb9f940
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 278 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ gui/
*.bat
*.eep
*.lss
tools_python/test_mul8.py
Binary file added PriceHax/PriceHax_21.apk
Binary file not shown.
9 changes: 5 additions & 4 deletions PriceHax/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ plugins {

android {
namespace 'org.furrtek.pricehax2'
compileSdk 32
compileSdk 33

defaultConfig {
applicationId "org.furrtek.pricehax2"
minSdk 24
targetSdk 32
versionCode 2
versionName "2.0"
targetSdk 33
versionCode 3
versionName "2.1"
ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86_64'
}

buildTypes {
release {
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
Expand Down
Binary file added PriceHax/app/src/main/ic_launcher-playstore.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 16 additions & 28 deletions PriceHax/app/src/main/java/org/furrtek/pricehax2/CameraPreview.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,17 @@ public void onConfigurationChanged(Configuration newConfig) {
//setCameraDisplayOrientation((Activity) this.context, this.mCamera);
}

//public CameraPreview(Context context, Camera camera, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) {
public CameraPreview(Context context, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) {
super(context);
this.context = context;

this.mCamera = getBackCamera();
this.previewCallback = previewCb;
this.autoFocusCallback = autoFocusCb;
this.mHolder.addCallback(this);
//this.mHolder.setType(3);
}

//this.cameraId = getBackCameraId();
public void surfaceCreated(SurfaceHolder holder) {
this.mCamera = getBackCamera();
//this.mHolder.setType(3);

// The old mode uses the onAutoFocus function and makes a timer to try to focus periodically.
// The drawback is the hunting of the camera.
Expand All @@ -51,24 +50,15 @@ public CameraPreview(Context context, PreviewCallback previewCb, AutoFocusCallba
this.oldAutoFocusMode = false;
if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
else if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
} else if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
else {
} else {
this.oldAutoFocusMode = true;
}

this.mCamera.setParameters(params);
//this.mCamera.setPreviewCallback(previewCb);
this.mCamera.startPreview();
}

public boolean isOldAutoFocusMode() {
return this.oldAutoFocusMode;
}

public void surfaceCreated(SurfaceHolder holder) {
try {
if (this.mCamera != null) {
this.mCamera.setPreviewDisplay(holder);
Expand All @@ -79,6 +69,14 @@ public void surfaceCreated(SurfaceHolder holder) {
}

public void surfaceDestroyed(SurfaceHolder holder) {
try {
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
} catch (Exception e) {
e.printStackTrace();
}
}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Expand All @@ -94,9 +92,8 @@ public void surfaceChanged(SurfaceHolder holder, int format, int width, int heig
this.mCamera.setPreviewCallback(this.previewCallback);
this.mCamera.startPreview();

if (this.oldAutoFocusMode) {
if (this.oldAutoFocusMode)
this.mCamera.autoFocus(this.autoFocusCallback);
}

} catch (Exception e2) {
Log.d("DBG", "Error starting camera preview: " + e2.getMessage());
Expand Down Expand Up @@ -128,26 +125,17 @@ public static void setCameraDisplayOrientation(Activity activity, android.hardwa
switch (rotation) {
case Surface.ROTATION_0:
degrees = 90;
//params.setPreviewSize(height, width);
break;
case Surface.ROTATION_90: degrees = 0; break;
case Surface.ROTATION_180: degrees = 0; break;
case Surface.ROTATION_270: degrees = 180; break;
}
Log.d("DBG", "Preview surface rotation: " + degrees);

int result;
result = degrees;
/*if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}*/
Camera.Size size = params.getPreviewSize();
Log.d("DBG", "getPreviewSize: " + size.width + "," + size.height);

camera.setDisplayOrientation(result);
camera.setDisplayOrientation(degrees);
camera.setParameters(params);
}
}
85 changes: 41 additions & 44 deletions PriceHax/app/src/main/java/org/furrtek/pricehax2/DMConvert.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.furrtek.pricehax2;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ProgressBar;
Expand All @@ -13,7 +14,8 @@

import static org.furrtek.pricehax2.DitherBitmap.floydSteinbergDithering;

public class DMConvert extends AsyncTask<Bitmap, Integer, DMImage> {
public class DMConvert extends AsyncTask<DMConvertParams, Integer, DMImage> {
// This does the color conversion, dithering, encoding and compression.
private ImageView mImageView;
private ProgressBar mProgressBar;
AsyncResponse delegate = null;
Expand All @@ -28,91 +30,85 @@ protected void onProgressUpdate(Integer... progress) {
}
@Override
protected void onPostExecute(DMImage dmimage) {
Log.d("PHX", "Convert done, bytestream size: " + dmimage.byteStreamBW.size());
Log.d("PHX", "Convert done, BW bytestream size: " + dmimage.byteStreamBW.size());
delegate.processFinish(dmimage);
}
@Override
protected DMImage doInBackground(Bitmap... selectedImage) {
int x, y, wi, hi;
int w, h;
protected DMImage doInBackground(DMConvertParams... params) {
int x, y, w, h;
int pixel;
int idx = 0;
Bitmap imageBW, imageBWR;
w = selectedImage[0].getWidth();
h = selectedImage[0].getHeight();
Bitmap imageIn = params[0].imageIn;
boolean dithering = params[0].dithering;

w = imageIn.getWidth();
h = imageIn.getHeight();
DMImage dmimage = new DMImage(w, h);

/*if (PLType == 1318) {
w = 0xD0;
h = 0x70;
} else {
w = 172;
h = 72;
}*/
wi = w;
hi = h;

BitSet bitstreamBW = new BitSet(wi * hi);
BitSet bitstreamBWR = new BitSet(wi * hi * 2);

Bitmap scaledimage = selectedImage[0];

//Convert to BW
Log.d("PHX", "Convert to BW");
imageBW = floydSteinbergDithering(scaledimage, false);
//dmimage.bitmapBW = imageBW;
BitSet bitstreamBW = new BitSet(w * h); // One plane
BitSet bitstreamBWR = new BitSet(w * h * 2); // Two planes

// Convert to BW
imageBW = dithering ? floydSteinbergDithering(imageIn, false) : imageIn;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, idx++) {
pixel = imageBW.getPixel(x, y);
if (Color.red(pixel) > 0)
if (Color.red(pixel) > 127) {
bitstreamBW.set(idx);
else
pixel = Color.WHITE;
} else {
bitstreamBW.clear(idx);
pixel = Color.BLACK;
}
dmimage.bitmapBW.setPixel(x, y, pixel);
}
}

//Convert to BWR
Log.d("PHX", "Convert to BWR");
imageBWR = floydSteinbergDithering(scaledimage, true);
//dmimage.bitmapBWR = imageBWR;
imageBWR = dithering ? floydSteinbergDithering(imageIn, true) : imageIn;
idx = 0;
int idxr = w * h;
int idxr = w * h; // Offset index for red layer
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, idx++, idxr++) {
pixel = imageBWR.getPixel(x, y);
if (Color.red(pixel) > 0) {
if (Color.green(pixel) > 0) {
if (Color.red(pixel) > 127) {
if (Color.green(pixel) > 127) {
bitstreamBWR.set(idx); // White
bitstreamBWR.set(idxr);
dmimage.bitmapBWR.setPixel(x, y, 0xFFFFFFFF);
pixel = Color.WHITE;
//dmimage.bitmapBWR.setPixel(x, y, 0xFFFFFFFF);
} else {
bitstreamBWR.clear(idx); // Red
bitstreamBWR.clear(idxr);
dmimage.bitmapBWR.setPixel(x, y, 0xFFFF0000);
pixel = Color.RED;
//dmimage.bitmapBWR.setPixel(x, y, 0xFFFF0000);
}
} else {
bitstreamBWR.clear(idx); // Black
bitstreamBWR.set(idxr);
dmimage.bitmapBWR.setPixel(x, y, 0xFF000000);
pixel = Color.BLACK;
//dmimage.bitmapBWR.setPixel(x, y, 0xFF000000);
}
dmimage.bitmapBWR.setPixel(x, y, pixel);
}
}

// Compress
BitSet bitstreamBW_RLE = RLECompress(bitstreamBW);
BitSet bitstreamBWR_RLE = RLECompress(bitstreamBWR);

publishProgress(90);

// Pack to bytes
dmimage.byteStreamBW = bitstreamBytes(bitstreamBW_RLE);
dmimage.byteStreamBWR = bitstreamBytes(bitstreamBWR_RLE);

publishProgress(100);

return dmimage;
}

private List<Byte> bitstreamBytes(BitSet bitstream) {
// Pack bitstream to bytes
List<Byte> result = new ArrayList<Byte>();
byte[] bytes = bitstream.toByteArray();
for (byte b : bytes) {
Expand All @@ -129,9 +125,9 @@ private List<Byte> bitstreamBytes(BitSet bitstream) {
int idx = result.size();
int klp = idx % 20;
if (klp > 0) klp = 20 - klp;
Log.d("PHX", String.format("Padding %d to %d", idx, idx + klp));
for (int bsc = 0; bsc < klp; bsc++)
result.add(new Byte((byte)0));
Log.d("PHX", String.format("Padded %d to %d", idx, idx + klp));

return result;
}
Expand All @@ -144,7 +140,6 @@ private BitSet RLECompress(BitSet bitstream) {
List<Integer> RLErun = new ArrayList<Integer>();

// RLE compress
Log.d("PHX", String.format("RLE compress, raw size = %d", bitstream.length()));
for (int m = 1; m <= j; m++) {
n = bitstream.get(m);
if (n == p) {
Expand All @@ -161,7 +156,7 @@ private BitSet RLECompress(BitSet bitstream) {
BitSet bitstreamRLE = new BitSet();
bitstreamRLE.set(0, bitstream.get(0));

Log.d("PHX", "Gen unary coded runs");
// Gen unary coded runs
int idx = 1;
for (int cnts : RLErun) {
bs = Integer.toBinaryString(cnts);
Expand All @@ -176,11 +171,13 @@ private BitSet RLECompress(BitSet bitstream) {
}
}

String dbg = "";
Log.d("PHX", String.format("RLE compress, %d -> %d", bitstream.length(), bitstreamRLE.length()));

/*String dbg = "";
for (int i = 0; i < 256; i++) {
dbg += (bitstreamRLE.get(i) ? "1" : "0");
}
Log.d("PHX", dbg);
Log.d("PHX", dbg);*/
return bitstreamRLE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.furrtek.pricehax2;

import android.graphics.Bitmap;

public class DMConvertParams {
Bitmap imageIn;
boolean dithering = false;

DMConvertParams(Bitmap imageIn, boolean dithering) {
this.imageIn = imageIn;
this.dithering = dithering;
}
}
30 changes: 15 additions & 15 deletions PriceHax/app/src/main/java/org/furrtek/pricehax2/DMGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ public static List<IRFrame> DMGenFrames(DMImage dmImage, boolean BWR, Long PLID,

// Wake-up frame
Byte[] payload = {(byte) 0x17, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload), 30, 200)); // TODO: Check delay and repeats
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload), 10, 400)); // 400 Same parameters as Python tools

// Start frame
int datalen = dmImage.byteStreamBW.size(); // TODO: Pass selected BW or BWR bytestream instead of dmImage
List<Byte> byteStream = BWR ? dmImage.byteStreamBWR : dmImage.byteStreamBW;
int datalen = byteStream.size();
int width = dmImage.bitmapBW.getWidth();
int height = dmImage.bitmapBW.getHeight();
Log.d("PHX", String.format("w,h: %d,%d", width, height));
Log.d("PHX", String.format("Dimensions: %d*%d", width, height));
int x = 0;
int y = 0;
Byte[] payload_start = {
Expand All @@ -44,31 +45,30 @@ public static List<IRFrame> DMGenFrames(DMImage dmImage, boolean BWR, Long PLID,
0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_start), 30, 1)); // TODO: Check delay and repeats
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_start), 10, 1));

// Data frames
Log.d("PHX", String.format("Datalen %d", datalen));
int ymax = (int) Math.ceil((double)datalen / 20);
Log.d("PHX", String.format("ymax %d", ymax));
for (y = 0; y < ymax; y++) {
Log.d("PHX", String.format("datalen: %d", datalen));
int frame_count = (int)Math.ceil((double)datalen / 20);
Log.d("PHX", String.format("frame_count: %d", frame_count));
for (int frame_idx = 0; frame_idx < frame_count; frame_idx++) {
Byte[] payload_data = new Byte[27];
Log.d("PHX", String.format("Gen data frame %d", y));
payload_data[0] = (byte) 0x34;
payload_data[1] = (byte) 0;
payload_data[2] = (byte) 0;
payload_data[3] = (byte) 0;
payload_data[4] = (byte) 0x20;
payload_data[5] = (byte) (y >> 8);
payload_data[6] = (byte) (y & 255);
for (int cp = 0; cp < 20; cp++) { // WAS 20
payload_data[7 + cp] = dmImage.byteStreamBW.get(cp + (y * 20));
payload_data[5] = (byte) (frame_idx >> 8);
payload_data[6] = (byte) (frame_idx & 255);
for (int cp = 0; cp < 20; cp++) {
payload_data[7 + cp] = byteStream.get(cp + (frame_idx * 20));
}
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_data), 30, 1)); // TODO: Check delay and repeats
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_data), 10, 1));
}

// Refresh frame
Byte[] payloadc = {(byte) 0x34, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payloadc), 30, 1)); // TODO: Check delay and repeats
frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payloadc), 10, 1));

return frames;
}
Expand Down

0 comments on commit cb9f940

Please sign in to comment.