Compare commits

...

11 commits

Author SHA1 Message Date
Olga Miller 94821b1697 Version 2.13 2025-05-24 10:31:47 +02:00
Olga Miller cfda4eff70
Merge pull request #18 from xdsopl/master
Temporarily opt out of Edge-to-Edge enforcement
2025-05-24 10:22:08 +02:00
Ahmet Inan 446f1da12b temporarily opt out of edge to edge enforcement 2025-05-06 10:05:14 +02:00
Olga Miller decdb191af Version 2.12 2025-01-06 14:15:53 +01:00
Olga Miller 70f4cbbaa3 Combined font file names to font family names,
changed to store the font family name instead of the font file path (as it was before the previous commit),
 added reading of existing system font files with file extension .otf (i.e., additionally to .ttf)
2024-12-30 16:37:14 +01:00
Olga Miller 5c50012f71 Updated loading of the system fonts (by listing all existing /system/fonts/*.ttf files),
removed adjustment of italic and bold selection for fonts with suffix "-Italic" or "-Bold"
2024-12-29 17:01:16 +01:00
Olga Miller 53b1f39ca9 Updated gradle (SdkVersion 35) 2024-12-29 12:26:58 +01:00
Olga Miller a3aa15e025 Updated description of the "Transform Image" menu item in README 2024-08-18 10:26:14 +02:00
Olga Miller 34429ad4c3 Improved README 2024-08-18 10:26:14 +02:00
Olga Miller 2beda7adfd fastlane: Updated description of the "Transform Image" menu item 2024-08-18 10:04:00 +02:00
Olga Miller d381a06e1a fastlane: Improved full_description 2024-08-18 10:04:00 +02:00
9 changed files with 203 additions and 127 deletions

View file

@ -1,51 +1,53 @@
![Icon](app/src/main/res/mipmap-xhdpi/ic_launcher.png) ![Icon](app/src/main/res/mipmap-xhdpi/ic_launcher.png)
# SSTV Encoder 2 # SSTV Encoder 2
This app sends images via Slow-Scan Television (SSTV). Image encoder for Slow-Scan Television (SSTV) audio signals
### Supported Modes ### Modes
**Martin Modes:** Martin 1, Martin 2 Supported SSTV modes:
**PD Modes:** PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290 * **Martin Modes**: Martin 1, Martin 2
**Scottie Modes:** Scottie 1, Scottie 2, Scottie DX * **PD Modes**: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
**Robot Modes:** Robot 36 Color, Robot 72 Color * **Robot Modes**: Robot 36 Color, Robot 72 Color
**Wraase Modes:** Wraase SC2 180 * **Scottie Modes**: Scottie 1, Scottie 2, Scottie DX
* **Wraase Modes**: Wraase SC2 180
The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode Specifications", 2000: The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode Specifications", 2000:
http://www.barberdsp.com/downloads/Dayton%20Paper.pdf http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
### Image ### Image
Tap "Take Picture" or "Pick Picture" menu button or To load an image:
use the Share option of any app like Gallery to load an image. * tap **"Take Picture"** or **"Pick Picture"** menu button, or
* use the **Share** option of an app like e.g. Gallery.
To keep the aspect ratio, black borders will be added if necessary. To keep the aspect ratio, black borders will be added if necessary.
Original image can be resend using another mode without reloading. Original image can be resend using another mode without reloading.
After image rotation or mode changing the image will be scaled to that mode's native size.
After image rotation or mode changing the image
will be scaled to that mode's native size.
After closing the app the loaded image will not be stored. After closing the app the loaded image will not be stored.
### Text Overlay ### Text Overlay
Single tap **to add** a text overlay. Actions for working with text overlays:
Single tap on text overlay **to edit** it. * Single tap **to add** a text overlay.
Long press **to move** text overlay. * Single tap on text overlay **to edit** it.
Remove the text **to remove** a text overlay. * Long press **to move** text overlay.
* Remove the text **to remove** a text overlay.
After closing the app all text overlays After closing the app all text overlays will be stored and reloaded when restarting.
will be stored and reloaded when restarting.
### Menu ### Menu
* **"Play"** - Sends the image. Available menu options:
* **"Stop"** - Stops the current sending and empties the queue. * **"Play"**: Sends the image
* **"Pick Picture"** - Opens an image viewer app to select a picture. * **"Stop"**: Stops the current sending and empties the queue
* **"Take Picture"** - Starts a camera app to take a picture. * **"Pick Picture"**: Opens an image viewer app to select a picture
* **"Save as WAVE File"** - Creates a wave file in the Music folder in SSTV Encoder album. * **"Take Picture"**: Starts a camera app to take a picture
* **"Rotate Image"** - Rotates the image by 90 degrees. * **"Save as WAVE File"**: Creates a wave file in the Music folder in SSTV Encoder album
* **"Modes"** - Lists all supported modes. * **"Transform Image"**:
* **"Rotate"**: Rotates the image by 90 degrees
* **"Reset"**: Resets image rotation and scaling
* **"Modes"**: Lists all supported modes
### Installation ### Installation

View file

@ -1,13 +1,13 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdk 34 compileSdk 35
defaultConfig { defaultConfig {
applicationId "om.sstvencoder" applicationId "om.sstvencoder"
minSdk 21 minSdk 21
targetSdk 34 targetSdk 35
versionCode 32 versionCode 34
versionName "2.11" versionName "2.13"
} }
buildTypes { buildTypes {
release { release {

View file

@ -18,10 +18,12 @@ package om.sstvencoder;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -52,8 +54,6 @@ public class EditTextActivity extends AppCompatActivity
public static final String EXTRA = "EDIT_TEXT_EXTRA"; public static final String EXTRA = "EDIT_TEXT_EXTRA";
private Label mLabel; private Label mLabel;
private EditColorMode mEditColor; private EditColorMode mEditColor;
private FontFamilySet mFontFamilySet;
private FontFamilySet.FontFamily mSelectedFontFamily;
private List<String> mFontFamilyNameList; private List<String> mFontFamilyNameList;
private CheckBox mEditItalic, mEditBold, mEditOutline; private CheckBox mEditItalic, mEditBold, mEditOutline;
private int mClearTextButtonWidth; private int mClearTextButtonWidth;
@ -77,7 +77,6 @@ public class EditTextActivity extends AppCompatActivity
mEditBold.setChecked(mLabel.getBold()); mEditBold.setChecked(mLabel.getBold());
mEditItalic.setChecked(mLabel.getItalic()); mEditItalic.setChecked(mLabel.getItalic());
initFontFamilySpinner(mLabel.getFamilyName()); initFontFamilySpinner(mLabel.getFamilyName());
updateBoldAndItalic();
mEditOutline.setChecked(mLabel.getOutline()); mEditOutline.setChecked(mLabel.getOutline());
initOutlineSizeSpinner(mLabel.getOutlineSize()); initOutlineSizeSpinner(mLabel.getOutlineSize());
findViewById(R.id.edit_color).setBackgroundColor(mLabel.getForeColor()); findViewById(R.id.edit_color).setBackgroundColor(mLabel.getForeColor());
@ -148,12 +147,10 @@ public class EditTextActivity extends AppCompatActivity
private void initFontFamilySpinner(String familyName) { private void initFontFamilySpinner(String familyName) {
Spinner spinner = findViewById(R.id.edit_font_family); Spinner spinner = findViewById(R.id.edit_font_family);
spinner.setOnItemSelectedListener(this); spinner.setOnItemSelectedListener(this);
mFontFamilySet = new FontFamilySet(this); mFontFamilyNameList = Utility.getSystemFontFamilyList();
mSelectedFontFamily = mFontFamilySet.getFontFamily(familyName);
mFontFamilyNameList = mFontFamilySet.getFontFamilyDisplayNameList();
spinner.setAdapter(new ArrayAdapter<>(this, spinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, mFontFamilyNameList)); android.R.layout.simple_spinner_dropdown_item, mFontFamilyNameList));
spinner.setSelection(mFontFamilyNameList.indexOf(mSelectedFontFamily.displayName)); spinner.setSelection(mFontFamilyNameList.indexOf(familyName));
} }
private void initTextSizeSpinner(float textSize) { private void initTextSizeSpinner(float textSize) {
@ -219,28 +216,7 @@ public class EditTextActivity extends AppCompatActivity
mLabel.setOutlineSize(positionToOutlineSize(position)); mLabel.setOutlineSize(positionToOutlineSize(position));
} }
else if (parentId == R.id.edit_font_family) { else if (parentId == R.id.edit_font_family) {
String displayName = mFontFamilyNameList.get(position); mLabel.setFamilyName(mFontFamilyNameList.get(position));
mSelectedFontFamily = mFontFamilySet.getFontFamilyFromDisplayName(displayName);
mLabel.setFamilyName(mSelectedFontFamily.name);
updateBoldAndItalic();
}
}
private void updateBoldAndItalic() {
boolean bold = mSelectedFontFamily.bold;
mEditBold.setEnabled(bold);
findViewById(R.id.text_bold).setEnabled(bold);
if (!mEditBold.isEnabled()) {
mEditBold.setChecked(false);
mLabel.setBold(false);
}
boolean italic = mSelectedFontFamily.italic;
mEditItalic.setEnabled(italic);
findViewById(R.id.text_italic).setEnabled(italic);
if (!mEditItalic.isEnabled()) {
mEditItalic.setChecked(false);
mLabel.setItalic(false);
} }
} }

View file

@ -22,7 +22,9 @@ import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Typeface; import android.graphics.Typeface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import om.sstvencoder.Utility;
class LabelPainter { class LabelPainter {
private interface IDrawer { private interface IDrawer {
@ -130,7 +132,10 @@ class LabelPainter {
private void setPaintSettings(float sizeFactor) { private void setPaintSettings(float sizeFactor) {
mPaint.setAlpha(255); mPaint.setAlpha(255);
mPaint.setTypeface(Typeface.create(mLabel.getFamilyName(), getTypeface())); try {
mPaint.setTypeface(createTypeface());
} catch (Exception ignore) {
}
setTextPaintSettings(); setTextPaintSettings();
setSizePaintSettings(sizeFactor); setSizePaintSettings(sizeFactor);
} }
@ -151,18 +156,22 @@ class LabelPainter {
mPaint.setStrokeWidth(mLabel.getOutlineSize() * textSize); mPaint.setStrokeWidth(mLabel.getOutlineSize() * textSize);
} }
private int getTypeface() { private Typeface createTypeface() {
int typeface = Typeface.NORMAL; int style = Typeface.NORMAL;
if (mLabel.getBold() && mLabel.getItalic()) if (mLabel.getBold() && mLabel.getItalic())
typeface = Typeface.BOLD_ITALIC; style = Typeface.BOLD_ITALIC;
else { else {
if (mLabel.getBold()) if (mLabel.getBold())
typeface = Typeface.BOLD; style = Typeface.BOLD;
else if (mLabel.getItalic()) else if (mLabel.getItalic())
typeface = Typeface.ITALIC; style = Typeface.ITALIC;
} }
return typeface;
String fontFilePath = Utility.getFontFilePath(mLabel.getFamilyName(), style);
Typeface family = Typeface.createFromFile(fontFilePath);
return Typeface.create(family, style);
} }
} }

View file

@ -18,7 +18,10 @@ package om.sstvencoder;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Rect; import android.graphics.Rect;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
@ -28,10 +31,15 @@ import androidx.core.content.FileProvider;
import java.io.File; import java.io.File;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Locale; import java.util.Locale;
final class Utility { public final class Utility {
private static final String DIRECTORY_SYSTEM_FONTS = "/system/fonts";
private static final String DEFAULT_FONT_FAMILY = "Default";
@NonNull @NonNull
static Rect getEmbeddedRect(int w, int h, int iw, int ih) { static Rect getEmbeddedRect(int w, int h, int iw, int ih) {
Rect rect; Rect rect;
@ -113,4 +121,94 @@ final class Utility {
String state = Environment.getExternalStorageState(); String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state); return Environment.MEDIA_MOUNTED.equals(state);
} }
static List<String> getSystemFontFamilyList() {
List<String> fontFamilyNameList = new ArrayList<>();
File fontsDir = new File(DIRECTORY_SYSTEM_FONTS);
if (fontsDir.exists() && fontsDir.isDirectory()) {
File[] files = fontsDir.listFiles();
if (files != null) {
for (File file : files) {
String fileName = file.getName();
if (file.isFile() && isSupportedFontFileFormat(fileName)) {
String fontFamilyName = getFontFamilyName(fileName);
if (!fontFamilyNameList.contains(fontFamilyName))
fontFamilyNameList.add(fontFamilyName);
}
}
}
}
fontFamilyNameList.add(0, Utility.DEFAULT_FONT_FAMILY);
return fontFamilyNameList;
}
private static boolean isSupportedFontFileFormat(String fileName) {
return fileName.endsWith(".ttf") || fileName.endsWith(".otf");
}
private static String getFontFamilyName(String fileName) {
String fontFamilyName = fileName;
int lastIndex = fileName.length() - 1;
int charIndex = fileName.indexOf('-');
if (0 < charIndex && charIndex < lastIndex) {
fontFamilyName = fileName.substring(0, charIndex);
} else {
charIndex = fileName.lastIndexOf('.');
if (0 < charIndex && charIndex < lastIndex) {
fontFamilyName = fileName.substring(0, charIndex);
}
}
return fontFamilyName;
}
public static String getFontFilePath(String fontFamilyName, int style) {
List<String> fontFamilyFilePathList = getFontFamilyFilePathList(fontFamilyName);
String fontFilePath = fontFamilyFilePathList.get(0);
String styleString = getFontFileStyleString(style);
if (!styleString.isEmpty()) {
for (String path : fontFamilyFilePathList) {
if (path.contains(styleString)) {
fontFilePath = path;
break;
}
}
}
return fontFilePath;
}
private static List<String> getFontFamilyFilePathList(String fontFamilyName) {
List<String> fontFamilyFilePathList = new ArrayList<>();
File fontsDir = new File(DIRECTORY_SYSTEM_FONTS);
if (fontsDir.exists() && fontsDir.isDirectory()) {
File[] files = fontsDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
String path = file.getAbsolutePath();
if (path.contains(fontFamilyName)) {
fontFamilyFilePathList.add(path);
}
}
}
}
}
return fontFamilyFilePathList;
}
private static String getFontFileStyleString(int style) {
if (style == Typeface.NORMAL)
return "-Regular";
if (style == Typeface.BOLD_ITALIC)
return "-BoldItalic";
if (style == Typeface.BOLD)
return "-Bold";
if (style == Typeface.ITALIC)
return "-Italic";
return "";
}
} }

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Base.Theme.AppCompat">
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>

View file

@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.5.2' classpath 'com.android.tools.build:gradle:8.7.3'
} }
} }

View file

@ -1,68 +1,53 @@
<h3>Supported Modes</h3> <h3>Modes</h3>
Supported SSTV modes:
<ul> <ul>
<li><b>Martin Modes:</b> Martin 1, Martin 2</li> <li><b>Martin Modes</b>: Martin 1, Martin 2</li>
<li><b>PD Modes:</b> PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290</li> <li><b>PD Modes</b>: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290</li>
<li><b>Scottie Modes:</b> Scottie 1, Scottie 2, Scottie DX</li> <li><b>Robot Modes</b>: Robot 36 Color, Robot 72 Color</li>
<li><b>Robot Modes:</b> Robot 36 Color, Robot 72 Color</li> <li><b>Scottie Modes</b>: Scottie 1, Scottie 2, Scottie DX</li>
<li><b>Wraase Modes:</b> Wraase SC2 180</li> <li><b>Wraase Modes</b>: Wraase SC2 180</li>
</ul> </ul>
The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode Specifications", 2000:<br>
<p> <a href='http://www.barberdsp.com/downloads/Dayton%20Paper.pdf'>http://www.barberdsp.com/downloads/Dayton%20Paper.pdf</a>
The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode
Specifications", 2000:
<br>
<a href='http://www.barberdsp.com/downloads/Dayton%20Paper.pdf'>http://www.barberdsp.com/downloads/Dayton%20Paper.pdf</a>
</p>
<h3>Image</h3> <h3>Image</h3>
<p> To load an image:
Tap "Take Picture" or "Pick Picture" menu button or <ul>
<br> <li>tap <b>"Take Picture"</b> or <b>"Pick Picture"</b> menu button, or
use the Share option of any app like Gallery to load an image. <li>use the <b>Share</b> option of an app like e.g. Gallery.
<br> </ul>
<br> To keep the aspect ratio, black borders will be added if necessary.<br>
To keep the aspect ratio, black borders will be added if necessary. Original image can be resend using another mode without reloading.<br>
<br> After image rotation or mode changing the image will be scaled to that mode's native size.<br>
Original image can be resend using another mode without reloading. After closing the app the loaded image will not be stored.
<br>
<br>
After image rotation or mode changing the image
<br>
will be scaled to that mode's native size.
<br>
<br>
After closing the app the loaded image will not be stored.
</p>
<h3>Text Overlay</h3> <h3>Text Overlay</h3>
<p> Actions for working with text overlays:
Single tap <b>to add</b> a text overlay. <ul>
<br> <li>Single tap <b>to add</b> a text overlay.</li>
Single tap on text overlay <b>to edit</b> it. <li>Single tap on text overlay <b>to edit</b> it.</li>
<br> <li>Long press <b>to move</b> text overlay.</li>
Long press <b>to move</b> text overlay. <li>Remove the text <b>to remove</b> a text overlay.</li>
<br> </ul>
Remove the text <b>to remove</b> a text overlay. After closing the app all text overlays will be stored and reloaded when restarting.
<br>
<br>
After closing the app all text overlays
<br>
will be stored and reloaded when restarting.
</p>
<h3>Menu</h3> <h3>Menu</h3>
Available menu options:
<ul> <ul>
<li><b>"Play"</b> - Sends the image.</li> <li><b>"Play"</b>: Sends the image</li>
<li><b>"Stop"</b> - Stops the current sending and empties the queue.</li> <li><b>"Stop"</b>: Stops the current sending and empties the queue</li>
<li><b>"Pick Picture"</b> - Opens an image viewer app to select a picture.</li> <li><b>"Pick Picture"</b>: Opens an image viewer app to select a picture</li>
<li><b>"Take Picture"</b> - Starts a camera app to take a picture.</li> <li><b>"Take Picture"</b>: Starts a camera app to take a picture</li>
<li><b>"Save as WAVE File"</b> - Creates a wave file in the Music folder in SSTV Encoder album. <li><b>"Save as WAVE File"</b>: Creates a wave file in the Music folder in SSTV Encoder album</li>
</li> <li><b>"Transform Image"</b>:</li>
<li><b>"Rotate Image"</b> - Rotates the image by 90 degrees.</li> <ul>
<li><b>"Modes"</b> - Lists all supported modes.</li> <li><b>"Rotate"</b>: Rotates the image by 90 degrees</li>
<li><b>"Reset"</b>: Resets image rotation and scaling</li>
</ul>
<li><b>"Modes"</b>: Lists all supported modes</li>
</ul> </ul>

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists