diff --git a/app/src/main/java/xdsopl/robot36/BaseMode.java b/app/src/main/java/xdsopl/robot36/BaseMode.java index fcdc7e8..64d7607 100644 --- a/app/src/main/java/xdsopl/robot36/BaseMode.java +++ b/app/src/main/java/xdsopl/robot36/BaseMode.java @@ -1,4 +1,8 @@ package xdsopl.robot36; public abstract class BaseMode implements Mode { + @Override + public int getEstimatedHorizontalShift() { + return 0; + } } diff --git a/app/src/main/java/xdsopl/robot36/HFFax.java b/app/src/main/java/xdsopl/robot36/HFFax.java index 49e0361..a526681 100644 --- a/app/src/main/java/xdsopl/robot36/HFFax.java +++ b/app/src/main/java/xdsopl/robot36/HFFax.java @@ -1,19 +1,21 @@ package xdsopl.robot36; +import android.graphics.Color; + public class HFFax extends BaseMode { private final ExponentialMovingAverage lowPassFilter; - private final int smallPictureMaxSamples; - private final int mediumPictureMaxSamples; private final String name; private final int sr; + private final float[] cumulated; + private int horizontalShift = 0; + HFFax(String name, int sampleRate) { this.name = name; - smallPictureMaxSamples = (int) Math.round(0.125 * sampleRate); - mediumPictureMaxSamples = (int) Math.round(0.175 * sampleRate); lowPassFilter = new ExponentialMovingAverage(); this.sr = sampleRate; + cumulated = new float[getWidth()]; } private float freqToLevel(float frequency, float offset) { @@ -55,6 +57,11 @@ public class HFFax extends BaseMode { return sr / 2; } + @Override + public int getEstimatedHorizontalShift() { + return horizontalShift; + } + @Override 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) { if (syncPulseIndex < 0 || syncPulseIndex + scanLineSamples > scanLineBuffer.length) return false; - int horizontalPixels = scopeBufferWidth; - if (scanLineSamples < smallPictureMaxSamples) - horizontalPixels /= 2; - if (scanLineSamples < mediumPictureMaxSamples) - horizontalPixels /= 2; + int horizontalPixels = getWidth(); lowPassFilter.cutoff(horizontalPixels, 2 * scanLineSamples, 2); lowPassFilter.reset(); for (int i = 0; i < scanLineSamples; ++i) - scratchBuffer[i] = lowPassFilter.avg(scanLineBuffer[syncPulseIndex + i]); + scratchBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); lowPassFilter.reset(); for (int i = scanLineSamples - 1; i >= 0; --i) scratchBuffer[i] = freqToLevel(lowPassFilter.avg(scratchBuffer[i]), frequencyOffset); for (int i = 0; i < horizontalPixels; ++i) { 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.height = 1; return true; diff --git a/app/src/main/java/xdsopl/robot36/MainActivity.java b/app/src/main/java/xdsopl/robot36/MainActivity.java index 09b30de..45b5879 100644 --- a/app/src/main/java/xdsopl/robot36/MainActivity.java +++ b/app/src/main/java/xdsopl/robot36/MainActivity.java @@ -15,6 +15,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; import android.media.AudioFormat; 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); if (currentMode == null || !currentMode.equals("HF Fax")) { 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); } diff --git a/app/src/main/java/xdsopl/robot36/Mode.java b/app/src/main/java/xdsopl/robot36/Mode.java index 36ae102..1994c4d 100644 --- a/app/src/main/java/xdsopl/robot36/Mode.java +++ b/app/src/main/java/xdsopl/robot36/Mode.java @@ -42,6 +42,11 @@ public interface Mode { */ int getScanLineSamples(); + /** + * @return number of pixels of horizontal shift based on recent data, nonzero for HF Fax + */ + int getEstimatedHorizontalShift(); + /** * Reset internal state. */