Detecting white margin in HF Fax mode and correcting image shift when saving

pull/38/head
Marek Ossowski 2025-08-16 22:09:29 +02:00
rodzic 9558080ff8
commit 638484ae78
4 zmienionych plików z 57 dodań i 11 usunięć

Wyświetl plik

@ -1,4 +1,8 @@
package xdsopl.robot36; package xdsopl.robot36;
public abstract class BaseMode implements Mode { public abstract class BaseMode implements Mode {
@Override
public int getEstimatedHorizontalShift() {
return 0;
}
} }

Wyświetl plik

@ -1,19 +1,21 @@
package xdsopl.robot36; package xdsopl.robot36;
import android.graphics.Color;
public class HFFax extends BaseMode { public class HFFax extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int smallPictureMaxSamples;
private final int mediumPictureMaxSamples;
private final String name; private final String name;
private final int sr; private final int sr;
private final float[] cumulated;
private int horizontalShift = 0;
HFFax(String name, int sampleRate) { HFFax(String name, int sampleRate) {
this.name = name; this.name = name;
smallPictureMaxSamples = (int) Math.round(0.125 * sampleRate);
mediumPictureMaxSamples = (int) Math.round(0.175 * sampleRate);
lowPassFilter = new ExponentialMovingAverage(); lowPassFilter = new ExponentialMovingAverage();
this.sr = sampleRate; this.sr = sampleRate;
cumulated = new float[getWidth()];
} }
private float freqToLevel(float frequency, float offset) { private float freqToLevel(float frequency, float offset) {
@ -55,6 +57,11 @@ public class HFFax extends BaseMode {
return sr / 2; return sr / 2;
} }
@Override
public int getEstimatedHorizontalShift() {
return horizontalShift;
}
@Override @Override
public void reset() { public void reset() {
} }
@ -63,22 +70,38 @@ public class HFFax extends BaseMode {
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) { public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
if (syncPulseIndex < 0 || syncPulseIndex + scanLineSamples > scanLineBuffer.length) if (syncPulseIndex < 0 || syncPulseIndex + scanLineSamples > scanLineBuffer.length)
return false; return false;
int horizontalPixels = scopeBufferWidth; int horizontalPixels = getWidth();
if (scanLineSamples < smallPictureMaxSamples)
horizontalPixels /= 2;
if (scanLineSamples < mediumPictureMaxSamples)
horizontalPixels /= 2;
lowPassFilter.cutoff(horizontalPixels, 2 * scanLineSamples, 2); lowPassFilter.cutoff(horizontalPixels, 2 * scanLineSamples, 2);
lowPassFilter.reset(); lowPassFilter.reset();
for (int i = 0; i < scanLineSamples; ++i) for (int i = 0; i < scanLineSamples; ++i)
scratchBuffer[i] = lowPassFilter.avg(scanLineBuffer[syncPulseIndex + i]); scratchBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]);
lowPassFilter.reset(); lowPassFilter.reset();
for (int i = scanLineSamples - 1; i >= 0; --i) for (int i = scanLineSamples - 1; i >= 0; --i)
scratchBuffer[i] = freqToLevel(lowPassFilter.avg(scratchBuffer[i]), frequencyOffset); scratchBuffer[i] = freqToLevel(lowPassFilter.avg(scratchBuffer[i]), frequencyOffset);
for (int i = 0; i < horizontalPixels; ++i) { for (int i = 0; i < horizontalPixels; ++i) {
int position = (i * scanLineSamples) / horizontalPixels; int position = (i * scanLineSamples) / horizontalPixels;
pixelBuffer.pixels[i] = ColorConverter.GRAY(scratchBuffer[position]); int color = ColorConverter.GRAY(scratchBuffer[position]);
pixelBuffer.pixels[i] = color;
cumulated[i] *= 0.99f; //decay old data
cumulated[i] += Color.luminance(color);
} }
//try to detect "sync": thick white margin
int bestIndex = 0;
float bestValue = 0;
for (int x = 0; x < getWidth(); ++x)
{
float val = cumulated[x];
if (val > bestValue)
{
bestIndex = x;
bestValue = val;
}
}
horizontalShift = bestIndex;
pixelBuffer.width = horizontalPixels; pixelBuffer.width = horizontalPixels;
pixelBuffer.height = 1; pixelBuffer.height = 1;
return true; return true;

Wyświetl plik

@ -15,6 +15,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioRecord; import android.media.AudioRecord;
@ -846,6 +847,19 @@ public class MainActivity extends AppCompatActivity {
Bitmap bmp = Bitmap.createBitmap(scopeBuffer.pixels, offset, stride, width, height, Bitmap.Config.ARGB_8888); Bitmap bmp = Bitmap.createBitmap(scopeBuffer.pixels, offset, stride, width, height, Bitmap.Config.ARGB_8888);
if (currentMode == null || !currentMode.equals("HF Fax")) { if (currentMode == null || !currentMode.equals("HF Fax")) {
bmp = Bitmap.createScaledBitmap(bmp, width / 3, height / 3, true); bmp = Bitmap.createScaledBitmap(bmp, width / 3, height / 3, true);
} else {
Mode mode = decoder.currentMode;
int shift = mode.getEstimatedHorizontalShift();
if (shift > 0) {
Bitmap part1 = Bitmap.createBitmap(bmp, 0, 0, shift, bmp.getHeight());
Bitmap part2 = Bitmap.createBitmap(bmp, shift, 0, mode.getWidth() - shift, bmp.getHeight());
Bitmap bmpMutable = Bitmap.createBitmap(mode.getWidth(), bmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new android.graphics.Canvas(bmpMutable);
canvas.drawBitmap(part2, 0, 1, null);
canvas.drawBitmap(part1, mode.getWidth() - shift, 0, null);
bmp = bmpMutable;
}
} }
storeBitmap(bmp); storeBitmap(bmp);
} }

Wyświetl plik

@ -42,6 +42,11 @@ public interface Mode {
*/ */
int getScanLineSamples(); int getScanLineSamples();
/**
* @return number of pixels of horizontal shift based on recent data, nonzero for HF Fax
*/
int getEstimatedHorizontalShift();
/** /**
* Reset internal state. * Reset internal state.
*/ */