Compare commits

...

41 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
Olga Miller 9e62733729 Version 2.11 2024-08-14 20:26:44 +02:00
Olga Miller 18fda14c97 Updated gradle (SdkVersion 34),
replaced deprecated 'buildDir',
added (now required) 'buildConfig true',
forced a specific version of Kotlin (to avoid the build error "Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules kotlin-stdlib-1.8.22.jar [..]")
2024-08-14 20:10:11 +02:00
Olga Miller 88823b2787 Replaced resource strings with constants for Mode names (to comply with requirements in updated Android Studio) 2024-08-14 20:10:11 +02:00
Olga Miller a7fe39fa73 Version 2.10 2024-01-14 13:26:42 +01:00
Olga Miller e213aa8d4b Linked Privacy Policy 2024-01-14 13:20:34 +01:00
Olga Miller 018b0930fc Corrected Chinese-Simplified translation of strings added to resources after PR15 merge
as suggested by WeiguangTWK, see https://github.com/olgamiller/SSTVEncoder2/pull/15#issuecomment-1880042737
2024-01-07 16:44:32 +01:00
Olga Miller f758fdcc40 Added to string resources: "Sending..." and "<file> saving..." 2023-12-18 12:25:36 +01:00
Olga Miller e7424cac3e Added to string resources: "Small", "Normal", "Large", "Huge", "Thin", "Normal", "Thick", "Default" 2023-12-18 11:56:45 +01:00
Olga Miller 33395413be Removed "Outline" from "outline_color" and "outline_size" for the default language 2023-12-18 11:12:41 +01:00
Olga Miller 2eefc0ca6a
Merge pull request #15 from WeiguangTWK/master
Add Chinese-Simplified translation
2023-12-18 10:25:06 +01:00
WeiguangTWK 12b17a5276 Apply new string to Edit_text site 2023-12-18 07:17:25 +08:00
WeiguangTWK 1617ec117e Update translation and add new string 2023-12-18 07:17:02 +08:00
WeiguangTWK 39c69a35ba Update translation 2023-12-18 07:04:47 +08:00
WeiguangTWK aeebe14341 Fix: app_name is not translatable 2023-12-16 20:44:45 +08:00
Weiguangtwk b21f5ba89f
Update strings.xml 2
Remove unnecessary string “别担心”
2023-12-10 18:27:48 +08:00
Weiguangtwk 8f0bf4c3ca
Update strings.xml according to comments
App name translation is removed, translation about 'outline' is corrected

ps: “轮廓线” means same as “描边” so I choose easy understanding “描边”
2023-12-10 18:24:46 +08:00
WeiguangTWK f9a48b5274 Check some untranslatable strings 2023-12-10 15:56:28 +08:00
WeiguangTWK 8daac61f3a Added Chinese-Simplified 2023-12-10 15:56:07 +08:00
Olga Miller 95500e4014 Updated build-tools 2023-08-27 14:59:54 +02:00
Olga Miller ff69230e4a Version 2.9 2023-08-27 11:43:40 +02:00
Olga Miller ec849bef77 Added "Reset" option (to reset image position, etc.) under "Transform Image" menu item, moved "Rotate" option there 2023-08-27 11:41:50 +02:00
Olga Miller 256463399a Gradle updates (SdkVersion 32) 2023-08-27 10:13:56 +02:00
Olga Miller 6401f1bdc5 Version 2.8 2023-03-05 13:56:19 +01:00
Olga Miller c6be28df56 Removed image size limit by replacing BitmapFactory.decodeStream with BitmapFactory.decodeByteArray, simplified the code a bit 2023-03-04 17:26:19 +01:00
Olga Miller 8ce13b9fc5 Added intent.action.PICK to AndroidManifest.xml
(on some Android 11 devices "Pick Picture" did not work)
2023-02-25 13:04:20 +01:00
Olga Miller fa71909df5 Moved resolving and starting another activity to tryToStartActivityForResult-method and added Toast messages 2023-02-25 13:02:15 +01:00
Olga Miller 117556f69b Linked the app icon in README 2023-02-05 13:05:46 +01:00
Olga Miller 184a641076 fastlane: Added icon and phoneScreenshots 2023-02-05 12:47:56 +01:00
Olga Miller da6cc08faa fastlane: Updated full_description.txt, added title.txt 2023-02-05 12:46:09 +01:00
Olga Miller 71b3b87c43 Updated README,removed text in German 2023-02-05 12:43:15 +01:00
41 changed files with 498 additions and 332 deletions

160
README.md
View file

@ -1,151 +1,75 @@
![Icon](app/src/main/res/mipmap-xhdpi/ic_launcher.png)
# SSTV Encoder 2
### Short Description
Image encoder for Slow-Scan Television (SSTV) audio signals
This app sends images via Slow Scan Television (SSTV).
### Modes
### Supported Modes
Supported SSTV modes:
* **Martin Modes**: Martin 1, Martin 2
* **PD Modes**: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
* **Robot Modes**: Robot 36 Color, Robot 72 Color
* **Scottie Modes**: Scottie 1, Scottie 2, Scottie DX
* **Wraase Modes**: Wraase SC2 180
**Martin Modes:** Martin 1, Martin 2
**PD Modes:** PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
**Scottie Modes:** Scottie 1, Scottie 2, Scottie DX
**Robot Modes:** Robot 36 Color, Robot 72 Color
**Wraase Modes:** Wraase SC2 180
The mode specifications are taken from the Dayton Paper of JL Barber:
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
### Image
Tap "Take Picture" or "Pick Picture" menu button or
use the Share option of any app like Gallery to load an image.
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.
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.
### Text Overlay
Single tap **to add** a text overlay.
Single tap on text overlay **to edit** it.
Long press **to move** text overlay.
Remove the text **to remove** a text overlay.
Actions for working with text overlays:
* Single tap **to add** a text overlay.
* Single tap on text overlay **to edit** it.
* Long press **to move** text overlay.
* Remove the text **to remove** a text overlay.
After closing the app all text overlays
will be stored and reloaded when restarting.
After closing the app all text overlays will be stored and reloaded when restarting.
### Menu
* "Pick Picture"
- Opens an image viewer app to select a picture.
* "Take Picture"
- Starts a camera app to take a picture.
* "Save Wave File"
- Creates a wave file in the Music folder in SSTV Encoder album.
* "Play"
- Sends the image.
* "Stop"
- Stops the current sending and empties the queue.
* "Rotate Image"
- Rotates the image by 90 degrees.
* "Modes"
- Lists all supported modes.
Available menu options:
* **"Play"**: Sends the image
* **"Stop"**: Stops the current sending and empties the queue
* **"Pick Picture"**: Opens an image viewer app to select a picture
* **"Take Picture"**: Starts a camera app to take a picture
* **"Save as WAVE File"**: Creates a wave file in the Music folder in SSTV Encoder album
* **"Transform Image"**:
* **"Rotate"**: Rotates the image by 90 degrees
* **"Reset"**: Resets image rotation and scaling
* **"Modes"**: Lists all supported modes
### Google Play
### Installation
On Google Play or F-Droid the Working App:
SSTV Encoder
The working app "SSTV Encoder" can be installed
on Google Play:
https://play.google.com/store/apps/details?id=om.sstvencoder
or on F-Droid:
https://f-droid.org/packages/om.sstvencoder/
### SSTV Image Decoder
# SSTV Image Decoder
Open Source Code:
https://github.com/xdsopl/robot36/tree/android
On Google Play or F-Droid the Working App:
Robot36 - SSTV Image Decoder
https://play.google.com/store/apps/details?id=xdsopl.robot36
https://f-droid.org/packages/xdsopl.robot36/
##### __________________ DEUTSCH __________________
# SSTV-Kodierer 2
### Kurzbeschreibung
Diese Applikation sendet Bilder via Slow Scan Television (SSTV).
### Unterstützte Modi
**Martin Modi:** Martin 1, Martin 2
**PD Modi:** PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
**Scottie Modi:** Scottie 1, Scottie 2, Scottie DX
**Robot Modi:** Robot 36 Color, Robot 72 Color
**Wraase Modi:** Wraase SC2 180
Modi-Spezifikation:
Dayton Paper of JL Barber
http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
### Bild
Bilder können durch das Berühren des "Take Picture" oder "Pick Picture" Menü-Knopfes oder
durch die "Teilen" (oder "Bild Teilen", "Share") Option anderer Applikationen wie "Galerie" geladen.
Um das Seitenverhältnis beizubehalten, werden (falls notwendig) schwarze Ränder hinzugefügt.
Das Originalbild kann in einem anderen Modus neu gesendet werden ohne das Bild neu laden zu müssen.
Nach dem Rotieren des Bildes oder nach dem Auswählen eines Übertragungsmodus
wird das Bild auf die native Größe skaliert.
Nach dem Schließen der Applikation wird das Bild nicht gespeichert.
### Text
Tippen der Bildfläche startet den Text-Editor.
Bewegen des Textes wird durch langes Berühren initiiert.
Nach dem Schließen der Applikation alle Texte werden gespeichert und wieder geladen beim Neustart.
### Menu
* "Pick Picture"
- Öffnet eine Applikation wie "Galerie" um ein Bild auszuwählen.
* "Take Picture"
- Startet eine Kamera-Applikation um ein Bild aufzunehmen.
* "Save Wave File"
- Erstellt eine Wave-Datei in "Musik" Ordner in "SSTV Encoder"-Album.
* "Play"
- Sendet das Bild.
* "Stop"
- Stoppt das aktuelle Senden und leert die Warteschlange.
* "Rotate Image"
- Rotiert das Bild um 90 Grad.
* "Modes"
- Listet alle unterstützten Modi.
### Google Play
Auf Google Play oder F-Droid die funktionierende Applikation:
SSTV Encoder
https://play.google.com/store/apps/details?id=om.sstvencoder
https://f-droid.org/packages/om.sstvencoder/
### SSTV-Dekodierer
Quelltext:
https://github.com/xdsopl/robot36/tree/android
Auf Google Play oder F-Droid die funktionierende Applikation:
Robot36 - SSTV Bild Dekodierer
### Installation
The working app "Robot36 - SSTV Image Decoder" can be installed
on Google Play:
https://play.google.com/store/apps/details?id=xdsopl.robot36
or on F-Droid:
https://f-droid.org/packages/xdsopl.robot36/

View file

@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 32
compileSdk 35
defaultConfig {
applicationId "om.sstvencoder"
minSdkVersion 21
targetSdkVersion 32
versionCode 28
versionName "2.7"
minSdk 21
targetSdk 35
versionCode 34
versionName "2.13"
}
buildTypes {
release {
@ -15,10 +15,14 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
namespace 'om.sstvencoder'
buildFeatures {
buildConfig true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation "androidx.exifinterface:exifinterface:1.3.5"
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation "androidx.exifinterface:exifinterface:1.3.7"
}

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="om.sstvencoder">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
@ -16,6 +15,15 @@
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE"/>
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
<data android:mimeType="image/*"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application
android:requestLegacyExternalStorage="true"

View file

@ -175,6 +175,19 @@ public class CropView extends AppCompatImageView {
invalidate();
}
public void resetImage() {
if (!mImageOK)
return;
if (mOrientation == 90 || mOrientation == 270) {
int tmp = mImageWidth;
mImageWidth = mImageHeight;
mImageHeight = tmp;
}
mOrientation = 0;
resetInputRect();
invalidate();
}
public void setNoBitmap() {
mImageOK = false;
mOrientation = 0;
@ -182,7 +195,7 @@ public class CropView extends AppCompatImageView {
invalidate();
}
public void setBitmap(@NonNull InputStream stream) throws IOException, IllegalArgumentException {
public void setBitmap(@NonNull InputStream stream) throws Exception {
mImageOK = false;
mOrientation = 0;
recycle();
@ -190,35 +203,48 @@ public class CropView extends AppCompatImageView {
invalidate();
}
private void loadImage(InputStream stream) throws IOException, IllegalArgumentException {
// app6 + exif
int bufferBytes = 1048576;
if (!stream.markSupported())
stream = new BufferedInputStream(stream, bufferBytes);
stream.mark(bufferBytes);
private void loadImage(InputStream stream) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new BufferedInputStream(stream), null, options);
stream.reset();
byte[] streamBytes = null;
String errorMessage = null;
try {
int length = stream.available();
if (length > 0) {
streamBytes = new byte[length];
if (length == stream.read(streamBytes, 0, streamBytes.length)) {
BitmapFactory.decodeByteArray(streamBytes, 0, streamBytes.length, options);
}
else
streamBytes = null;
}
} catch (Exception ex) {
errorMessage = ex.getMessage();
streamBytes = null;
}
mImageWidth = options.outWidth;
mImageHeight = options.outHeight;
if (mImageWidth * mImageHeight < 1024 * 1024) {
mCacheBitmap = BitmapFactory.decodeStream(stream);
mSmallImage = true;
} else {
mRegionDecoder = BitmapRegionDecoder.newInstance(stream, true);
mCacheRect.setEmpty();
mSmallImage = false;
if (streamBytes != null && mImageWidth > 0 && mImageHeight > 0) {
mSmallImage = mImageWidth * mImageHeight < 1024 * 1024;
if (mSmallImage) {
mCacheBitmap = BitmapFactory.decodeByteArray(streamBytes, 0, streamBytes.length, null);
} else {
mRegionDecoder = BitmapRegionDecoder.newInstance(streamBytes, 0, streamBytes.length, true);
mCacheRect.setEmpty();
}
}
if (mCacheBitmap == null && mRegionDecoder == null) {
String size = options.outWidth + "x" + options.outHeight;
String message = "Stream could not be decoded. Image size: " + size;
if (mImageWidth <= 0 || mImageHeight <= 0)
throw new IllegalArgumentException(message);
else
throw new IOException(message);
String message = errorMessage;
if (message == null) {
message = "Stream could not be decoded.";
if (mImageWidth > 0 && mImageHeight > 0) {
message += " Image size: " + mImageWidth + "x" + mImageHeight;
}
}
throw new Exception(message);
}
mImageOK = true;

View file

@ -18,10 +18,12 @@ package om.sstvencoder;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import androidx.annotation.ColorInt;
import androidx.fragment.app.DialogFragment;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@ -52,8 +54,6 @@ public class EditTextActivity extends AppCompatActivity
public static final String EXTRA = "EDIT_TEXT_EXTRA";
private Label mLabel;
private EditColorMode mEditColor;
private FontFamilySet mFontFamilySet;
private FontFamilySet.FontFamily mSelectedFontFamily;
private List<String> mFontFamilyNameList;
private CheckBox mEditItalic, mEditBold, mEditOutline;
private int mClearTextButtonWidth;
@ -77,7 +77,6 @@ public class EditTextActivity extends AppCompatActivity
mEditBold.setChecked(mLabel.getBold());
mEditItalic.setChecked(mLabel.getItalic());
initFontFamilySpinner(mLabel.getFamilyName());
updateBoldAndItalic();
mEditOutline.setChecked(mLabel.getOutline());
initOutlineSizeSpinner(mLabel.getOutlineSize());
findViewById(R.id.edit_color).setBackgroundColor(mLabel.getForeColor());
@ -148,18 +147,22 @@ public class EditTextActivity extends AppCompatActivity
private void initFontFamilySpinner(String familyName) {
Spinner spinner = findViewById(R.id.edit_font_family);
spinner.setOnItemSelectedListener(this);
mFontFamilySet = new FontFamilySet();
mSelectedFontFamily = mFontFamilySet.getFontFamily(familyName);
mFontFamilyNameList = mFontFamilySet.getFontFamilyDisplayNameList();
mFontFamilyNameList = Utility.getSystemFontFamilyList();
spinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, mFontFamilyNameList));
spinner.setSelection(mFontFamilyNameList.indexOf(mSelectedFontFamily.displayName));
spinner.setSelection(mFontFamilyNameList.indexOf(familyName));
}
private void initTextSizeSpinner(float textSize) {
Spinner spinner = findViewById(R.id.edit_text_size);
spinner.setOnItemSelectedListener(this);
String[] sizeList = new String[]{"Small", "Normal", "Large", "Huge"};
String[] sizeList = new String[]
{
getString(R.string.font_size_small),
getString(R.string.font_size_normal),
getString(R.string.font_size_large),
getString(R.string.font_size_huge)
};
spinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, sizeList));
spinner.setSelection(textSizeToPosition(textSize));
@ -168,7 +171,12 @@ public class EditTextActivity extends AppCompatActivity
private void initOutlineSizeSpinner(float outlineSize) {
Spinner spinner = findViewById(R.id.edit_outline_size);
spinner.setOnItemSelectedListener(this);
String[] sizeList = new String[]{"Thin", "Normal", "Thick"};
String[] sizeList = new String[]
{
getString(R.string.outline_size_thin),
getString(R.string.outline_size_normal),
getString(R.string.outline_size_thick)
};
spinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, sizeList));
spinner.setSelection(outlineSizeToPosition(outlineSize));
@ -208,28 +216,7 @@ public class EditTextActivity extends AppCompatActivity
mLabel.setOutlineSize(positionToOutlineSize(position));
}
else if (parentId == R.id.edit_font_family) {
String displayName = 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);
mLabel.setFamilyName(mFontFamilyNameList.get(position));
}
}

View file

@ -66,7 +66,8 @@ class Encoder {
mode = mQueue.remove(0);
}
mode.init();
mProgressBar.begin(mode.getProcessCount(), "Sending...");
mProgressBar.begin(mode.getProcessCount(),
mMessenger.getString(R.string.progressbar_message_sending));
while (mode.process()) {
mProgressBar.step();
@ -122,7 +123,8 @@ class Encoder {
@Override
public void run() {
mode.init();
mProgressBar2.begin(mode.getProcessCount(), context.getFileName() + " saving...");
mProgressBar2.begin(mode.getProcessCount(),
mMessenger.getString(R.string.progressbar_message_saving_to_file, context.getFileName()));
while (mode.process()) {
mProgressBar2.step();

View file

@ -16,6 +16,8 @@ limitations under the License.
package om.sstvencoder;
import androidx.annotation.NonNull;
import android.content.Context;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@ -37,8 +39,10 @@ class FontFamilySet {
}
private final List<FontFamily> mFamilySet;
private final Context mContext;
FontFamilySet() {
FontFamilySet(Context context) {
mContext = context;
mFamilySet = new ArrayList<>();
fillWithSystemFonts(mFamilySet);
if (mFamilySet.size() == 0)
@ -49,7 +53,7 @@ class FontFamilySet {
private FontFamily getDefaultFontFamily() {
FontFamily defaultFontFamily = new FontFamily();
defaultFontFamily.name = null;
defaultFontFamily.displayName = "Default";
defaultFontFamily.displayName = mContext.getString(R.string.font_default);
defaultFontFamily.bold = true;
defaultFontFamily.italic = true;
return defaultFontFamily;

View file

@ -119,41 +119,30 @@ public class MainActivity extends AppCompatActivity {
// Set verbose to false for any Uri that might have expired (e.g. shared from browser).
private boolean loadImage(Uri uri, boolean verbose) {
boolean succeeded = false;
ContentResolver resolver = getContentResolver();
InputStream stream = null;
if (uri != null) {
mSettings.setImageUri(uri);
try {
stream = resolver.openInputStream(uri);
InputStream stream = resolver.openInputStream(uri);
if (stream != null)
mCropView.setBitmap(stream);
succeeded = true;
} catch (Exception ex) { // e.g. FileNotFoundException, SecurityException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isPermissionException(ex)
&& needsRequestReadPermission()) {
requestReadPermission(REQUEST_LOAD_IMAGE_PERMISSION);
return false;
}
showFileNotLoadedMessage(ex, verbose);
else
showFileNotLoadedMessage(ex, verbose);
}
}
if (stream == null || !loadImage(stream, resolver, uri)) {
if (succeeded) {
mCropView.rotateImage(getOrientation(resolver, uri));
mSettings.setImageUri(uri);
}
else
setDefaultBitmap();
return false;
}
return true;
}
private boolean loadImage(InputStream stream, ContentResolver resolver, Uri uri) {
try {
mCropView.setBitmap(stream);
} catch (IllegalArgumentException ex) {
Toast.makeText(this, ex.getMessage(), Toast.LENGTH_LONG).show();
return false;
} catch (Exception ex) {
String s = Utility.createMessage(ex) + "\n\n" + uri;
showErrorMessage(getString(R.string.load_img_err_title), ex.getMessage(), s);
return false;
}
mCropView.rotateImage(getOrientation(resolver, uri));
return true;
return succeeded;
}
private void setDefaultBitmap() {
@ -343,19 +332,32 @@ public class MainActivity extends AppCompatActivity {
else if (id == R.id.action_rotate) {
mCropView.rotateImage(90);
}
else if (id == R.id.action_reset) {
mCropView.resetImage();
}
else if (id == R.id.action_privacy_policy) {
showTextPage(getString(R.string.action_privacy_policy), getString(R.string.action_privacy_policy_text));
openLinkInBrowser("https://sites.google.com/view/olgamiller/sstvencoder/privacypolicy/");
}
else if (id == R.id.action_about) {
showTextPage(getString(R.string.action_about), getString(R.string.action_about_text, BuildConfig.VERSION_NAME));
}
else if (id != R.id.action_modes) {
else if (id != R.id.action_modes && id != R.id.action_transform) {
String className = item.getIntent().getStringExtra(CLASS_NAME);
setMode(className);
}
return true;
}
private void openLinkInBrowser(String link) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
else {
showErrorMessage(getString(R.string.another_activity_start_err), link, "");
}
}
private void showTextPage(String title, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
@ -391,25 +393,34 @@ public class MainActivity extends AppCompatActivity {
public void startEditTextActivity(@NonNull Label label) {
Intent intent = new Intent(this, EditTextActivity.class);
intent.putExtra(EditTextActivity.EXTRA, label);
startActivityForResult(intent, EditTextActivity.REQUEST_CODE);
tryToStartActivityForResult(intent, EditTextActivity.REQUEST_CODE);
}
private void dispatchTakePictureIntent() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getPackageManager()) != null) {
Uri uri = Utility.createImageUri(this);
if (uri != null) {
mSettings.setImageUri(uri);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
Uri uri = Utility.createImageUri(this);
if (uri != null) {
mSettings.setImageUri(uri);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
tryToStartActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
private void dispatchPickPictureIntent() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
if (intent.resolveActivity(getPackageManager()) != null)
startActivityForResult(intent, REQUEST_PICK_IMAGE);
tryToStartActivityForResult(intent, REQUEST_PICK_IMAGE);
}
private void tryToStartActivityForResult(Intent intent, int requestCode) {
if (intent.resolveActivity(getPackageManager()) == null) {
Toast.makeText(this, R.string.another_activity_resolve_err, Toast.LENGTH_LONG).show();
return;
}
try {
startActivityForResult(intent, requestCode);
} catch (Exception ignore) {
Toast.makeText(this, R.string.another_activity_start_err, Toast.LENGTH_LONG).show();
}
}
@Override

View file

@ -36,4 +36,8 @@ class MainActivityMessenger {
}
});
}
public String getString(int resId, Object... formatArgs) {
return mMainActivity.getString(resId, formatArgs);
}
}

View file

@ -16,7 +16,7 @@ limitations under the License.
package om.sstvencoder.ModeInterfaces;
public interface IModeInfo {
int getModeName();
String getModeName();
String getModeClassName();

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_martin1)
@ModeDescription(name = Martin1.Name)
class Martin1 extends Martin {
public static final String Name = "Martin 1";
Martin1(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 44;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_martin2)
@ModeDescription(name = Martin2.Name)
class Martin2 extends Martin {
public static final String Name = "Martin 2";
Martin2(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 40;

View file

@ -23,5 +23,5 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface ModeDescription {
int name();
String name();
}

View file

@ -25,7 +25,7 @@ class ModeInfo implements IModeInfo {
mModeClass = modeClass;
}
public int getModeName() {
public String getModeName() {
return mModeClass.getAnnotation(ModeDescription.class).name();
}

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 640, height = 496)
@ModeDescription(name = R.string.action_pd120)
@ModeDescription(name = PD120.Name)
class PD120 extends PD {
public static final String Name = "PD 120";
PD120(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 95;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 512, height = 400)
@ModeDescription(name = R.string.action_pd160)
@ModeDescription(name = PD160.Name)
class PD160 extends PD {
public static final String Name = "PD 160";
PD160(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 98;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 640, height = 496)
@ModeDescription(name = R.string.action_pd180)
@ModeDescription(name = PD180.Name)
class PD180 extends PD {
public static final String Name = "PD 180";
PD180(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 96;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 640, height = 496)
@ModeDescription(name = R.string.action_pd240)
@ModeDescription(name = PD240.Name)
class PD240 extends PD {
public static final String Name = "PD 240";
PD240(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 97;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 800, height = 616)
@ModeDescription(name = R.string.action_pd290)
@ModeDescription(name = PD290.Name)
class PD290 extends PD {
public static final String Name = "PD 290";
PD290(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 94;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_pd50)
@ModeDescription(name = PD50.Name)
class PD50 extends PD {
public static final String Name = "PD 50";
PD50(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 93;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_pd90)
@ModeDescription(name = PD90.Name)
class PD90 extends PD {
public static final String Name = "PD 90";
PD90(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 99;

View file

@ -22,11 +22,12 @@ import om.sstvencoder.Modes.ImageFormats.YuvFactory;
import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 240)
@ModeDescription(name = R.string.action_robot36)
@ModeDescription(name = Robot36.Name)
class Robot36 extends Mode {
public static final String Name = "Robot 36";
private final Yuv mYuv;
private final int mLumaScanSamples;

View file

@ -22,11 +22,12 @@ import om.sstvencoder.Modes.ImageFormats.YuvFactory;
import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 240)
@ModeDescription(name = R.string.action_robot72)
@ModeDescription(name = Robot72.Name)
class Robot72 extends Mode {
public static final String Name = "Robot 72";
private final Yuv mYuv;
private final int mLumaScanSamples;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_scottie1)
@ModeDescription(name = Scottie1.Name)
class Scottie1 extends Scottie {
public static final String Name = "Scottie 1";
Scottie1(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 60;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_scottie2)
@ModeDescription(name = Scottie2.Name)
class Scottie2 extends Scottie {
public static final String Name = "Scottie 2";
Scottie2(Bitmap bitmap, IOutput output){
super(bitmap, output);
mVISCode = 56;

View file

@ -19,11 +19,12 @@ import android.graphics.Bitmap;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_scottie_dx)
@ModeDescription(name = ScottieDX.Name)
class ScottieDX extends Scottie {
public static final String Name = "Scottie DX";
ScottieDX(Bitmap bitmap, IOutput output) {
super(bitmap, output);
mVISCode = 76;

View file

@ -20,11 +20,12 @@ import android.graphics.Color;
import om.sstvencoder.ModeInterfaces.ModeSize;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.R;
@ModeSize(width = 320, height = 256)
@ModeDescription(name = R.string.action_wraaseSC2_180)
@ModeDescription(name = Wraase.Name)
class Wraase extends Mode {
public static final String Name = "Wraase SC2 180";
private final int mSyncPulseSamples;
private final double mSyncPulseFrequency;

View file

@ -22,7 +22,9 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import androidx.annotation.NonNull;
import om.sstvencoder.Utility;
class LabelPainter {
private interface IDrawer {
@ -130,7 +132,10 @@ class LabelPainter {
private void setPaintSettings(float sizeFactor) {
mPaint.setAlpha(255);
mPaint.setTypeface(Typeface.create(mLabel.getFamilyName(), getTypeface()));
try {
mPaint.setTypeface(createTypeface());
} catch (Exception ignore) {
}
setTextPaintSettings();
setSizePaintSettings(sizeFactor);
}
@ -151,18 +156,22 @@ class LabelPainter {
mPaint.setStrokeWidth(mLabel.getOutlineSize() * textSize);
}
private int getTypeface() {
int typeface = Typeface.NORMAL;
private Typeface createTypeface() {
int style = Typeface.NORMAL;
if (mLabel.getBold() && mLabel.getItalic())
typeface = Typeface.BOLD_ITALIC;
style = Typeface.BOLD_ITALIC;
else {
if (mLabel.getBold())
typeface = Typeface.BOLD;
style = Typeface.BOLD;
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.Intent;
import android.graphics.Rect;
import androidx.exifinterface.media.ExifInterface;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@ -28,10 +31,15 @@ import androidx.core.content.FileProvider;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
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
static Rect getEmbeddedRect(int w, int h, int iw, int ih) {
Rect rect;
@ -113,4 +121,94 @@ final class Utility {
String state = Environment.getExternalStorageState();
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

@ -91,7 +91,7 @@
<TextView
android:id="@+id/text_outline_size"
android:text="@string/size"/>
android:text="@string/outline_size" />
<Spinner
android:id="@+id/edit_outline_size"
@ -126,7 +126,7 @@
<TextView
android:id="@+id/text_outline_color"
android:text="@string/color"/>
android:text="@string/outline_color" />
<LinearLayout
android:clickable="true"

View file

@ -30,10 +30,22 @@
android:title="@string/action_save_wave"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_rotate"
android:icon="@android:drawable/ic_menu_rotate"
android:title="@string/action_rotate"
app:showAsAction="ifRoom"/>
android:id="@+id/action_transform"
android:title="@string/action_transform"
app:showAsAction="ifRoom">
<menu>
<item
android:id="@+id/action_rotate"
android:icon="@android:drawable/ic_menu_rotate"
android:title="@string/action_rotate"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_reset"
android:icon="@android:drawable/ic_menu_revert"
android:title="@string/action_reset"
app:showAsAction="ifRoom"/>
</menu>
</item>
<item
android:id="@+id/action_modes"
android:title="@string/action_modes"

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

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_pick_picture">选择照片</string>
<string name="action_take_picture">拍摄照片</string>
<string name="action_save_wave">保存为波形(.wav)文件</string>
<string name="action_stop">停止</string>
<string name="action_play">播放</string>
<string name="action_transform">更改图片</string>
<string name="action_rotate">旋转</string>
<string name="action_reset">重置</string>
<string name="action_done">完成</string>
<string name="action_modes">编码模式</string>
<string name="action_privacy_policy">隐私政策</string>
<string name="action_about">关于SSTV Encoder</string>
<string name="action_about_text">"\n SSTV Encoder %1$s\n版权所有 2017 Olga Miller\n \n\nSSTV Encoder通过慢扫描电视/Slow Scan Television (SSTV)发送图片.\n \n\n要获取详细信息请参阅本软件源码: \nhttps://github.com/olgamiller/SSTVEncoder2\n \n\nDISCLAIMER:\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n "</string>
<string name="load_img_err_title">加载图像时出错</string>
<string name="load_img_orientation_err_title">定位图像时出错</string>
<string name="load_img_err_txt_unsupported">不受支持的内容</string>
<string name="message_prev_img_not_loaded">先前所使用的图像未能被加载</string>
<string name="message_no_camera">这个设备没有摄像头</string>
<string name="progressbar_message_sending">发送…</string>
<string name="progressbar_message_saving_to_file">%1$s 另存为文件…</string>
<string name="another_activity_resolve_err">未能解析另一个活动</string>
<string name="another_activity_start_err">另一个活动未能被启动</string>
<string name="btn_send_email">发送邮件</string>
<string name="btn_ok">好的</string>
<string name="email_subject">SSTV Encoder - BUG反馈</string>
<string name="chooser_title">发送BUG反馈</string>
<string name="bold">粗体</string>
<string name="italic">斜体</string>
<string name="outline">描边</string>
<string name="color">颜色</string>
<string name="outline_color">描边颜色</string>
<string name="size">大小</string>
<string name="font">字体</string>
<string name="font_default">默认字体</string>
<string name="text">文本</string>
<string name="font_size_small"></string>
<string name="font_size_normal">常规</string>
<string name="font_size_large"></string>
<string name="font_size_huge">很大</string>
<string name="outline_size">描边大小</string>
<string name="outline_size_thin"></string>
<string name="outline_size_normal">常规</string>
<string name="outline_size_thick"></string>
</resources>

View file

@ -1,38 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">SSTV Encoder</string>
<string name="app_name" translatable="false">SSTV Encoder</string>
<string name="action_pick_picture">Pick Picture</string>
<string name="action_take_picture">Take Picture</string>
<string name="action_save_wave">Save as WAVE File</string>
<string name="action_stop">Stop</string>
<string name="action_play">Play</string>
<string name="action_rotate">Rotate Image</string>
<string name="action_transform">Transform Image</string>
<string name="action_rotate">Rotate</string>
<string name="action_reset">Reset</string>
<string name="action_done">Done</string>
<string name="action_modes">Modes</string>
<string name="action_martin1">Martin 1</string>
<string name="action_martin2">Martin 2</string>
<string name="action_pd50">PD 50</string>
<string name="action_pd90">PD 90</string>
<string name="action_pd120">PD 120</string>
<string name="action_pd160">PD 160</string>
<string name="action_pd180">PD 180</string>
<string name="action_pd240">PD 240</string>
<string name="action_pd290">PD 290</string>
<string name="action_scottie1">Scottie 1</string>
<string name="action_scottie2">Scottie 2</string>
<string name="action_scottie_dx">Scottie DX</string>
<string name="action_robot36">Robot 36</string>
<string name="action_robot72">Robot 72</string>
<string name="action_wraaseSC2_180">Wraase SC2 180</string>
<string name="action_privacy_policy">Privacy Policy</string>
<string name="action_privacy_policy_text">
SSTV Encoder converts images to audio.
\n\nTo convert the image, it will be loaded into temporary memory.
\n\nThe loaded image can be scaled and rotated. Neither the modified image nor the modifications will be stored. The link to the last loaded image will be stored in the app settings locally.
\n\nA text overlay can be added. It will be stored in the app settings locally.
\n\nThe modified image with text overlays will be encoded to audio using the selected mode. The selected mode will be stored in the app settings locally.
\n\nThe resulting audio can either be played back by pressing the play button or saved locally on the device by pressing the save button.
</string>
<string name="action_about">About SSTV Encoder</string>
<string name="action_about_text">
SSTV Encoder %1$s\nCopyright 2017 Olga Miller
@ -45,6 +24,10 @@
<string name="load_img_err_txt_unsupported">Unsupported content.</string>
<string name="message_prev_img_not_loaded">Previous image could not be loaded.</string>
<string name="message_no_camera">Device has no camera.</string>
<string name="progressbar_message_sending">Sending…</string>
<string name="progressbar_message_saving_to_file">%1$s saving…</string>
<string name="another_activity_resolve_err">Another activity could not be resolved.</string>
<string name="another_activity_start_err">Another activity could not be started.</string>
<string name="btn_send_email">Send Email</string>
<string name="btn_ok">OK</string>
<string name="email_subject">SSTV Encoder - Bug Report</string>
@ -53,8 +36,17 @@
<string name="italic">Italic</string>
<string name="outline">Outline</string>
<string name="color">Color</string>
<string name="outline_color">Outline Color</string>
<string name="outline_color">Color</string>
<string name="size">Size</string>
<string name="font">Font</string>
<string name="font_default">Default</string>
<string name="text">Text</string>
<string name="font_size_small">Small</string>
<string name="font_size_normal">Normal</string>
<string name="font_size_large">Large</string>
<string name="font_size_huge">Huge</string>
<string name="outline_size">Size</string>
<string name="outline_size_thin">Thin</string>
<string name="outline_size_normal">Normal</string>
<string name="outline_size_thick">Thick</string>
</resources>

View file

@ -4,17 +4,24 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.android.tools.build:gradle:8.7.3'
}
}
allprojects {
configurations.configureEach {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.jetbrains.kotlin') {
details.useVersion "1.8.22"
}
}
}
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
tasks.register('clean', Delete) {
delete rootProject.layout.buildDirectory.get().asFile
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1 @@
SSTV Encoder

View file

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