├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── raw
│ │ │ └── smpte_color_bars.png
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── sym_keyboard_done_lxx_dark.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── sym_keyboard_done_lxx_dark.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── sym_keyboard_done_lxx_dark.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── sym_keyboard_done_lxx_dark.png
│ │ ├── xml
│ │ │ └── paths.xml
│ │ ├── values-v35
│ │ │ └── styles.xml
│ │ ├── layout
│ │ │ ├── fragment_color.xml
│ │ │ ├── activity_main.xml
│ │ │ └── activity_edit_text.xml
│ │ ├── menu
│ │ │ ├── menu_edit_text.xml
│ │ │ └── menu_main.xml
│ │ └── values-zh-rCN
│ │ │ └── strings.xml
│ │ ├── java
│ │ └── om
│ │ │ └── sstvencoder
│ │ │ ├── ModeInterfaces
│ │ │ ├── IModeInfo.java
│ │ │ ├── IMode.java
│ │ │ └── ModeSize.java
│ │ │ ├── Modes
│ │ │ ├── ImageFormats
│ │ │ │ ├── YuvImageFormat.java
│ │ │ │ ├── YuvFactory.java
│ │ │ │ ├── Yuv.java
│ │ │ │ ├── YuvConverter.java
│ │ │ │ ├── YUY2.java
│ │ │ │ ├── YUV440P.java
│ │ │ │ ├── NV21.java
│ │ │ │ └── YV12.java
│ │ │ ├── ModeDescription.java
│ │ │ ├── PD50.java
│ │ │ ├── PD90.java
│ │ │ ├── PD120.java
│ │ │ ├── PD160.java
│ │ │ ├── PD180.java
│ │ │ ├── PD240.java
│ │ │ ├── PD290.java
│ │ │ ├── Martin1.java
│ │ │ ├── Martin2.java
│ │ │ ├── Scottie1.java
│ │ │ ├── Scottie2.java
│ │ │ ├── ScottieDX.java
│ │ │ ├── ModeInfo.java
│ │ │ ├── Wraase.java
│ │ │ ├── PD.java
│ │ │ ├── ModeFactory.java
│ │ │ ├── Martin.java
│ │ │ ├── Scottie.java
│ │ │ ├── Robot72.java
│ │ │ ├── Robot36.java
│ │ │ └── Mode.java
│ │ │ ├── Output
│ │ │ ├── IOutput.java
│ │ │ ├── OutputFactory.java
│ │ │ ├── AudioOutput.java
│ │ │ ├── WaveFileOutputContext.java
│ │ │ └── WaveFileOutput.java
│ │ │ ├── ColorPalette
│ │ │ ├── IColorPalette.java
│ │ │ ├── ColorPaletteView.java
│ │ │ └── GridColorPalette.java
│ │ │ ├── TextOverlay
│ │ │ ├── Position.java
│ │ │ ├── IReader.java
│ │ │ ├── IWriter.java
│ │ │ ├── Label.java
│ │ │ ├── LabelContainer.java
│ │ │ └── LabelCollection.java
│ │ │ ├── MainActivityMessenger.java
│ │ │ ├── ProgressBarWrapper.java
│ │ │ ├── ColorFragment.java
│ │ │ ├── Encoder.java
│ │ │ ├── Settings.java
│ │ │ ├── TextOverlayTemplate.java
│ │ │ ├── FontFamilySet.java
│ │ │ ├── Utility.java
│ │ │ └── EditTextActivity.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── title.txt
│ ├── short_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── Screenshot_1.png
│ │ └── Screenshot_2.png
│ └── full_description.txt
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── NOTICE
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | SSTV Encoder
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Image encoder for Slow-Scan Television (SSTV) audio signals
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/smpte_color_bars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/raw/smpte_color_bars.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/xml/paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/sym_keyboard_done_lxx_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-hdpi/sym_keyboard_done_lxx_dark.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/sym_keyboard_done_lxx_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-mdpi/sym_keyboard_done_lxx_dark.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/sym_keyboard_done_lxx_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-xhdpi/sym_keyboard_done_lxx_dark.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/sym_keyboard_done_lxx_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/app/src/main/res/mipmap-xxhdpi/sym_keyboard_done_lxx_dark.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olgamiller/SSTVEncoder2/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_2.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v35/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | SSTV Encoder 2
2 | Copyright 2017 Olga Miller
3 |
4 | Mode specifications were taken from "Dayton Paper" of JL Barber:
5 | http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
6 |
7 | sym_keyboard_done_lxx_dark.png icons were taken from
8 | android.googlesource.com
9 |
10 | ic_launcher.png were created using GIMP:
11 | http://www.gimp.org/
12 |
13 | SMPTE Color Bars image (CC BY-SA 3.0) was taken from Wikipedia:
14 | http://en.wikipedia.org/wiki/SMPTE_color_bars#mediaviewer/File:SMPTE_Color_Bars.svg
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_edit_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/olga/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdk 35
5 | defaultConfig {
6 | applicationId "om.sstvencoder"
7 | minSdk 21
8 | targetSdk 35
9 | versionCode 34
10 | versionName "2.13"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 | namespace 'om.sstvencoder'
19 | buildFeatures {
20 | buildConfig true
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(dir: 'libs', include: ['*.jar'])
26 | implementation 'androidx.appcompat:appcompat:1.7.0'
27 | implementation "androidx.exifinterface:exifinterface:1.3.7"
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | android.useAndroidX=true
15 | android.enableJetifier=true
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ModeInterfaces/IModeInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ModeInterfaces;
17 |
18 | public interface IModeInfo {
19 | String getModeName();
20 |
21 | String getModeClassName();
22 |
23 | ModeSize getModeSize();
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ModeInterfaces/IMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ModeInterfaces;
17 |
18 | public interface IMode {
19 | void init();
20 |
21 | int getProcessCount();
22 |
23 | boolean process();
24 |
25 | void finish(boolean cancel);
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YuvImageFormat.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.ImageFormat;
19 |
20 | public class YuvImageFormat extends ImageFormat {
21 | public static final int YUV440P = 0x50303434;
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Output/IOutput.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Output;
17 |
18 | public interface IOutput {
19 | double getSampleRate();
20 |
21 | void init(int samples);
22 |
23 | void write(double value);
24 |
25 | void finish(boolean cancel);
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ColorPalette/IColorPalette.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ColorPalette;
17 |
18 | import android.graphics.Canvas;
19 |
20 | interface IColorPalette {
21 | void updateSize(float width, float height);
22 |
23 | void draw(Canvas canvas);
24 |
25 | int getSelectedColor();
26 |
27 | boolean selectColor(float x, float y);
28 |
29 | boolean selectColor(int color);
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ModeDescription.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import java.lang.annotation.ElementType;
19 | import java.lang.annotation.Retention;
20 | import java.lang.annotation.RetentionPolicy;
21 | import java.lang.annotation.Target;
22 |
23 | @Target({ElementType.TYPE})
24 | @Retention(RetentionPolicy.RUNTIME)
25 | @interface ModeDescription {
26 | String name();
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ModeInterfaces/ModeSize.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ModeInterfaces;
17 |
18 | import java.lang.annotation.ElementType;
19 | import java.lang.annotation.Retention;
20 | import java.lang.annotation.RetentionPolicy;
21 | import java.lang.annotation.Target;
22 |
23 | @Target({ElementType.TYPE})
24 | @Retention(RetentionPolicy.RUNTIME)
25 | public @interface ModeSize {
26 | int width();
27 |
28 | int height();
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Output/OutputFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Output;
17 |
18 | public final class OutputFactory {
19 |
20 | public static IOutput createOutputForSending() {
21 | double sampleRate = 44100.0;
22 | return new AudioOutput(sampleRate);
23 | }
24 |
25 | public static IOutput createOutputForSavingAsWave(WaveFileOutputContext context) {
26 | double sampleRate = 44100.0;
27 | return new WaveFileOutput(context, sampleRate);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/Position.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | class Position {
19 | private float mX;
20 | private float mY;
21 |
22 | Position() {
23 | mX = 0f;
24 | mY = 0f;
25 | }
26 |
27 | void set(float x, float y) {
28 | mX = x;
29 | mY = y;
30 | }
31 |
32 | void offset(float x, float y) {
33 | mX += x;
34 | mY += y;
35 | }
36 |
37 | float getX() {
38 | return mX;
39 | }
40 |
41 | float getY() {
42 | return mY;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD50.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = PD50.Name)
25 | class PD50 extends PD {
26 | public static final String Name = "PD 50";
27 |
28 | PD50(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 93;
31 | mColorScanDurationMs = 91.52;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD90.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = PD90.Name)
25 | class PD90 extends PD {
26 | public static final String Name = "PD 90";
27 |
28 | PD90(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 99;
31 | mColorScanDurationMs = 170.24;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD120.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 640, height = 496)
24 | @ModeDescription(name = PD120.Name)
25 | class PD120 extends PD {
26 | public static final String Name = "PD 120";
27 |
28 | PD120(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 95;
31 | mColorScanDurationMs = 121.6;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD160.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 512, height = 400)
24 | @ModeDescription(name = PD160.Name)
25 | class PD160 extends PD {
26 | public static final String Name = "PD 160";
27 |
28 | PD160(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 98;
31 | mColorScanDurationMs = 195.584;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD180.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 640, height = 496)
24 | @ModeDescription(name = PD180.Name)
25 | class PD180 extends PD {
26 | public static final String Name = "PD 180";
27 |
28 | PD180(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 96;
31 | mColorScanDurationMs = 183.04;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD240.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 640, height = 496)
24 | @ModeDescription(name = PD240.Name)
25 | class PD240 extends PD {
26 | public static final String Name = "PD 240";
27 |
28 | PD240(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 97;
31 | mColorScanDurationMs = 244.48;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD290.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 800, height = 616)
24 | @ModeDescription(name = PD290.Name)
25 | class PD290 extends PD {
26 | public static final String Name = "PD 290";
27 |
28 | PD290(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 94;
31 | mColorScanDurationMs = 228.8;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Martin1.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = Martin1.Name)
25 | class Martin1 extends Martin {
26 | public static final String Name = "Martin 1";
27 |
28 | Martin1(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 44;
31 | mColorScanDurationMs = 146.432;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Martin2.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = Martin2.Name)
25 | class Martin2 extends Martin {
26 | public static final String Name = "Martin 2";
27 |
28 | Martin2(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 40;
31 | mColorScanDurationMs = 73.216;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Scottie1.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = Scottie1.Name)
25 | class Scottie1 extends Scottie {
26 | public static final String Name = "Scottie 1";
27 |
28 | Scottie1(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 60;
31 | mColorScanDurationMs = 138.24;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Scottie2.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = Scottie2.Name)
25 | class Scottie2 extends Scottie {
26 | public static final String Name = "Scottie 2";
27 |
28 | Scottie2(Bitmap bitmap, IOutput output){
29 | super(bitmap, output);
30 | mVISCode = 56;
31 | mColorScanDurationMs = 88.064;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ScottieDX.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.ModeSize;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | @ModeSize(width = 320, height = 256)
24 | @ModeDescription(name = ScottieDX.Name)
25 | class ScottieDX extends Scottie {
26 | public static final String Name = "Scottie DX";
27 |
28 | ScottieDX(Bitmap bitmap, IOutput output) {
29 | super(bitmap, output);
30 | mVISCode = 76;
31 | mColorScanDurationMs = 345.6;
32 | mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/IReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | import java.io.IOException;
19 |
20 | public interface IReader {
21 | void beginRootObject() throws IOException;
22 |
23 | void beginObject() throws IOException;
24 |
25 | void endObject() throws IOException;
26 |
27 | void beginArray() throws IOException;
28 |
29 | void endArray() throws IOException;
30 |
31 | boolean hasNext() throws IOException;
32 |
33 | String readString() throws IOException;
34 |
35 | boolean readBoolean() throws IOException;
36 |
37 | float readFloat() throws IOException;
38 |
39 | int readInt() throws IOException;
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ModeInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import om.sstvencoder.ModeInterfaces.IModeInfo;
19 | import om.sstvencoder.ModeInterfaces.ModeSize;
20 |
21 | class ModeInfo implements IModeInfo {
22 | private final Class> mModeClass;
23 |
24 | ModeInfo(Class> modeClass) {
25 | mModeClass = modeClass;
26 | }
27 |
28 | public String getModeName() {
29 | return mModeClass.getAnnotation(ModeDescription.class).name();
30 | }
31 |
32 | public String getModeClassName() {
33 | return mModeClass.getName();
34 | }
35 |
36 | public ModeSize getModeSize() {
37 | return mModeClass.getAnnotation(ModeSize.class);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YuvFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | public final class YuvFactory {
21 | public static Yuv createYuv(Bitmap bitmap, int format) {
22 | switch (format) {
23 | case YuvImageFormat.YV12:
24 | return new YV12(bitmap);
25 | case YuvImageFormat.NV21:
26 | return new NV21(bitmap);
27 | case YuvImageFormat.YUY2:
28 | return new YUY2(bitmap);
29 | case YuvImageFormat.YUV440P:
30 | return new YUV440P(bitmap);
31 | default:
32 | throw new IllegalArgumentException("Only support YV12, NV21, YUY2 and YUV440P");
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/Yuv.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | public abstract class Yuv {
21 | protected byte[] mYuv;
22 | final int mWidth;
23 | final int mHeight;
24 |
25 | Yuv(Bitmap bitmap) {
26 | mWidth = bitmap.getWidth();
27 | mHeight = bitmap.getHeight();
28 | convertBitmapToYuv(bitmap);
29 | }
30 |
31 | protected abstract void convertBitmapToYuv(Bitmap bitmap);
32 |
33 | public int getWidth() {
34 | return mWidth;
35 | }
36 |
37 | public int getHeight() {
38 | return mHeight;
39 | }
40 |
41 | public abstract int getY(int x, int y);
42 |
43 | public abstract int getU(int x, int y);
44 |
45 | public abstract int getV(int x, int y);
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/IWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | import androidx.annotation.NonNull;
19 |
20 | import java.io.IOException;
21 |
22 | public interface IWriter {
23 | void beginRootObject() throws IOException;
24 |
25 | void beginObject(@NonNull String name) throws IOException;
26 |
27 | void endObject() throws IOException;
28 |
29 | void beginArray(@NonNull String name) throws IOException;
30 |
31 | void endArray() throws IOException;
32 |
33 | void write(@NonNull String name, String value) throws IOException;
34 |
35 | void write(@NonNull String name, boolean value) throws IOException;
36 |
37 | void write(@NonNull String name, float value) throws IOException;
38 |
39 | void write(@NonNull String name, int value) throws IOException;
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/MainActivityMessenger.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.os.Handler;
19 |
20 | import om.sstvencoder.Output.WaveFileOutputContext;
21 |
22 | class MainActivityMessenger {
23 | private final MainActivity mMainActivity;
24 | private final Handler mHandler;
25 |
26 | MainActivityMessenger(MainActivity activity) {
27 | mMainActivity = activity;
28 | mHandler = new Handler();
29 | }
30 |
31 | void carrySaveAsWaveIsDoneMessage(final WaveFileOutputContext context) {
32 | mHandler.post(new Runnable() {
33 | @Override
34 | public void run() {
35 | mMainActivity.completeSaving(context);
36 | }
37 | });
38 | }
39 |
40 | public String getString(int resId, Object... formatArgs) {
41 | return mMainActivity.getString(resId, formatArgs);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YuvConverter.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Color;
19 |
20 | final class YuvConverter {
21 | static int convertToY(int color) {
22 | double R = Color.red(color);
23 | double G = Color.green(color);
24 | double B = Color.blue(color);
25 | return clamp(16.0 + (.003906 * ((65.738 * R) + (129.057 * G) + (25.064 * B))));
26 | }
27 |
28 | static int convertToU(int color) {
29 | double R = Color.red(color);
30 | double G = Color.green(color);
31 | double B = Color.blue(color);
32 | return clamp(128.0 + (.003906 * ((-37.945 * R) + (-74.494 * G) + (112.439 * B))));
33 | }
34 |
35 | static int convertToV(int color) {
36 | double R = Color.red(color);
37 | double G = Color.green(color);
38 | double B = Color.blue(color);
39 | return clamp(128.0 + (.003906 * ((112.439 * R) + (-94.154 * G) + (-18.285 * B))));
40 | }
41 |
42 | private static int clamp(double value) {
43 | return value < 0.0 ? 0 : (value > 255.0 ? 255 : (int) value);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YUY2.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | class YUY2 extends Yuv {
21 | YUY2(Bitmap bitmap) {
22 | super(bitmap);
23 | }
24 |
25 | protected void convertBitmapToYuv(Bitmap bitmap) {
26 | mYuv = new byte[2 * mWidth * mHeight];
27 |
28 | for (int pos = 0, h = 0; h < mHeight; ++h) {
29 | for (int w = 0; w < mWidth; w += 2) {
30 | mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
31 | int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
32 | int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
33 | mYuv[pos++] = (byte) ((u0 + u1) / 2);
34 | mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w + 1, h));
35 | int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
36 | int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
37 | mYuv[pos++] = (byte) ((v0 + v1) / 2);
38 | }
39 | }
40 | }
41 |
42 | public int getY(int x, int y) {
43 | return 255 & mYuv[2 * mWidth * y + 2 * x];
44 | }
45 |
46 | public int getU(int x, int y) {
47 | return 255 & mYuv[2 * mWidth * y + (((x & ~1) << 1) | 1)];
48 | }
49 |
50 | public int getV(int x, int y) {
51 | return 255 & mYuv[2 * mWidth * y + ((x << 1) | 3)];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
24 |
25 |
31 |
32 |
37 |
38 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YUV440P.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | class YUV440P extends Yuv {
21 | YUV440P(Bitmap bitmap) {
22 | super(bitmap);
23 | }
24 |
25 | protected void convertBitmapToYuv(Bitmap bitmap) {
26 | mYuv = new byte[2 * mWidth * mHeight];
27 | int pos = 0;
28 |
29 | for (int h = 0; h < mHeight; ++h)
30 | for (int w = 0; w < mWidth; ++w)
31 | mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
32 |
33 | for (int h = 0; h < mHeight; h += 2) {
34 | for (int w = 0; w < mWidth; ++w) {
35 | int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
36 | int u1 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
37 | mYuv[pos++] = (byte) ((u0 + u1) / 2);
38 | }
39 | }
40 |
41 | for (int h = 0; h < mHeight; h += 2) {
42 | for (int w = 0; w < mWidth; ++w) {
43 | int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
44 | int v1 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
45 | mYuv[pos++] = (byte) ((v0 + v1) / 2);
46 | }
47 | }
48 | }
49 |
50 | public int getY(int x, int y) {
51 | return 255 & mYuv[mWidth * y + x];
52 | }
53 |
54 | public int getU(int x, int y) {
55 | return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + x];
56 | }
57 |
58 | public int getV(int x, int y) {
59 | return 255 & mYuv[((3 * mWidth * mHeight) >> 1) + mWidth * (y >> 1) + x];
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 |
2 | Modes
3 |
4 | Supported SSTV modes:
5 |
6 | - Martin Modes: Martin 1, Martin 2
7 | - PD Modes: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
8 | - Robot Modes: Robot 36 Color, Robot 72 Color
9 | - Scottie Modes: Scottie 1, Scottie 2, Scottie DX
10 | - Wraase Modes: Wraase SC2 180
11 |
12 | The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode Specifications", 2000:
13 | http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
14 |
15 | Image
16 |
17 | To load an image:
18 |
19 | - tap "Take Picture" or "Pick Picture" menu button, or
20 |
- use the Share option of an app like e.g. Gallery.
21 |
22 | To keep the aspect ratio, black borders will be added if necessary.
23 | Original image can be resend using another mode without reloading.
24 | After image rotation or mode changing the image will be scaled to that mode's native size.
25 | After closing the app the loaded image will not be stored.
26 |
27 | Text Overlay
28 |
29 | Actions for working with text overlays:
30 |
31 | - Single tap to add a text overlay.
32 | - Single tap on text overlay to edit it.
33 | - Long press to move text overlay.
34 | - Remove the text to remove a text overlay.
35 |
36 | After closing the app all text overlays will be stored and reloaded when restarting.
37 |
38 | Menu
39 |
40 | Available menu options:
41 |
42 | - "Play": Sends the image
43 | - "Stop": Stops the current sending and empties the queue
44 | - "Pick Picture": Opens an image viewer app to select a picture
45 | - "Take Picture": Starts a camera app to take a picture
46 | - "Save as WAVE File": Creates a wave file in the Music folder in SSTV Encoder album
47 | - "Transform Image":
48 |
49 | - "Rotate": Rotates the image by 90 degrees
50 | - "Reset": Resets image rotation and scaling
51 |
52 | - "Modes": Lists all supported modes
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/NV21.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | class NV21 extends Yuv {
21 | NV21(Bitmap bitmap) {
22 | super(bitmap);
23 | }
24 |
25 | protected void convertBitmapToYuv(Bitmap bitmap) {
26 | mYuv = new byte[(3 * mWidth * mHeight) / 2];
27 | int pos = 0;
28 |
29 | for (int h = 0; h < mHeight; ++h)
30 | for (int w = 0; w < mWidth; ++w)
31 | mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
32 |
33 | for (int h = 0; h < mHeight; h += 2) {
34 | for (int w = 0; w < mWidth; w += 2) {
35 | int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
36 | int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
37 | int v2 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
38 | int v3 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h + 1));
39 | mYuv[pos++] = (byte) ((v0 + v1 + v2 + v3) / 4);
40 | int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
41 | int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
42 | int u2 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
43 | int u3 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h + 1));
44 | mYuv[pos++] = (byte) ((u0 + u1 + u2 + u3) / 4);
45 | }
46 | }
47 | }
48 |
49 | public int getY(int x, int y) {
50 | return 255 & mYuv[mWidth * y + x];
51 | }
52 |
53 | public int getU(int x, int y) {
54 | return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + (x | 1)];
55 | }
56 |
57 | public int getV(int x, int y) {
58 | return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + (x & ~1)];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
66 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ImageFormats/YV12.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes.ImageFormats;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | class YV12 extends Yuv {
21 | YV12(Bitmap bitmap) {
22 | super(bitmap);
23 | }
24 |
25 | protected void convertBitmapToYuv(Bitmap bitmap) {
26 | mYuv = new byte[(3 * mWidth * mHeight) / 2];
27 | int pos = 0;
28 |
29 | for (int h = 0; h < mHeight; ++h)
30 | for (int w = 0; w < mWidth; ++w)
31 | mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
32 |
33 | for (int h = 0; h < mHeight; h += 2) {
34 | for (int w = 0; w < mWidth; w += 2) {
35 | int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
36 | int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
37 | int u2 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
38 | int u3 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h + 1));
39 | mYuv[pos++] = (byte) ((u0 + u1 + u2 + u3) / 4);
40 | }
41 | }
42 |
43 | for (int h = 0; h < mHeight; h += 2) {
44 | for (int w = 0; w < mWidth; w += 2) {
45 | int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
46 | int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
47 | int v2 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
48 | int v3 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h + 1));
49 | mYuv[pos++] = (byte) ((v0 + v1 + v2 + v3) / 4);
50 | }
51 | }
52 | }
53 |
54 | public int getY(int x, int y) {
55 | return 255 & mYuv[mWidth * y + x];
56 | }
57 |
58 | public int getU(int x, int y) {
59 | return 255 & mYuv[mWidth * mHeight + (mWidth >> 1) * (y >> 1) + (x >> 1)];
60 | }
61 |
62 | public int getV(int x, int y) {
63 | return 255 & mYuv[((5 * mWidth * mHeight) >> 2) + (mWidth >> 1) * (y >> 1) + (x >> 1)];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # SSTV Encoder 2
3 |
4 | Image encoder for Slow-Scan Television (SSTV) audio signals
5 |
6 | ### Modes
7 |
8 | Supported SSTV modes:
9 | * **Martin Modes**: Martin 1, Martin 2
10 | * **PD Modes**: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
11 | * **Robot Modes**: Robot 36 Color, Robot 72 Color
12 | * **Scottie Modes**: Scottie 1, Scottie 2, Scottie DX
13 | * **Wraase Modes**: Wraase SC2 180
14 |
15 | The mode specifications are taken from the Dayton Paper, JL Barber, "Proposal for SSTV Mode Specifications", 2000:
16 | http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
17 |
18 | ### Image
19 |
20 | To load an image:
21 | * tap **"Take Picture"** or **"Pick Picture"** menu button, or
22 | * use the **Share** option of an app like e.g. Gallery.
23 |
24 | To keep the aspect ratio, black borders will be added if necessary.
25 | Original image can be resend using another mode without reloading.
26 | After image rotation or mode changing the image will be scaled to that mode's native size.
27 | After closing the app the loaded image will not be stored.
28 |
29 | ### Text Overlay
30 |
31 | Actions for working with text overlays:
32 | * Single tap **to add** a text overlay.
33 | * Single tap on text overlay **to edit** it.
34 | * Long press **to move** text overlay.
35 | * Remove the text **to remove** a text overlay.
36 |
37 | After closing the app all text overlays will be stored and reloaded when restarting.
38 |
39 | ### Menu
40 |
41 | Available menu options:
42 | * **"Play"**: Sends the image
43 | * **"Stop"**: Stops the current sending and empties the queue
44 | * **"Pick Picture"**: Opens an image viewer app to select a picture
45 | * **"Take Picture"**: Starts a camera app to take a picture
46 | * **"Save as WAVE File"**: Creates a wave file in the Music folder in SSTV Encoder album
47 | * **"Transform Image"**:
48 | * **"Rotate"**: Rotates the image by 90 degrees
49 | * **"Reset"**: Resets image rotation and scaling
50 | * **"Modes"**: Lists all supported modes
51 |
52 | ### Installation
53 |
54 | The working app "SSTV Encoder" can be installed
55 |
56 | on Google Play:
57 | https://play.google.com/store/apps/details?id=om.sstvencoder
58 |
59 | or on F-Droid:
60 | https://f-droid.org/packages/om.sstvencoder/
61 |
62 | # SSTV Image Decoder
63 |
64 | Open Source Code:
65 | https://github.com/xdsopl/robot36/tree/android
66 |
67 | ### Installation
68 |
69 | The working app "Robot36 - SSTV Image Decoder" can be installed
70 |
71 | on Google Play:
72 | https://play.google.com/store/apps/details?id=xdsopl.robot36
73 |
74 | or on F-Droid:
75 | https://f-droid.org/packages/xdsopl.robot36/
76 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Output/AudioOutput.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Output;
17 |
18 | import android.media.AudioFormat;
19 | import android.media.AudioManager;
20 | import android.media.AudioTrack;
21 |
22 | class AudioOutput implements IOutput {
23 | private final double mSampleRate;
24 | private short[] mAudioBuffer;
25 | private AudioTrack mAudioTrack;
26 | private int mBufferPos;
27 |
28 | AudioOutput(double sampleRate) {
29 | mSampleRate = sampleRate;
30 | mBufferPos = 0;
31 | }
32 |
33 | @Override
34 | public void init(int samples) {
35 | mAudioBuffer = new short[(5 * (int) mSampleRate) / 2]; // 2.5 seconds of buffer
36 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
37 | (int) mSampleRate, AudioFormat.CHANNEL_OUT_MONO,
38 | AudioFormat.ENCODING_PCM_16BIT, mAudioBuffer.length * 2,
39 | AudioTrack.MODE_STREAM);
40 | mAudioTrack.play();
41 | }
42 |
43 | @Override
44 | public double getSampleRate() {
45 | return mSampleRate;
46 | }
47 |
48 | @Override
49 | public void write(double value) {
50 | if (mBufferPos == mAudioBuffer.length) {
51 | mAudioTrack.write(mAudioBuffer, 0, mAudioBuffer.length);
52 | mBufferPos = 0;
53 | }
54 |
55 | mAudioBuffer[mBufferPos++] = (short) (value * Short.MAX_VALUE);
56 | }
57 |
58 | @Override
59 | public void finish(boolean cancel) {
60 | if (mAudioTrack != null) {
61 | if (!cancel)
62 | drainBuffer();
63 | mAudioTrack.stop();
64 | mAudioTrack.release();
65 | mAudioTrack = null;
66 | mAudioBuffer = null;
67 | }
68 | }
69 |
70 | private void drainBuffer() {
71 | // The second run makes sure that the previous buffer indeed got played
72 | for (int i = 0; i < 2; ++i) {
73 | while (mBufferPos < mAudioBuffer.length)
74 | mAudioBuffer[mBufferPos++] = 0;
75 | mAudioTrack.write(mAudioBuffer, 0, mAudioBuffer.length);
76 | mBufferPos = 0;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
39 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ProgressBarWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.os.Handler;
19 | import android.view.View;
20 | import android.widget.ProgressBar;
21 | import android.widget.TextView;
22 |
23 | class ProgressBarWrapper {
24 | private final ProgressBar mProgressBar;
25 | private final TextView mText;
26 | private final Handler mHandler;
27 | private final int mSteps;
28 | private int mLastStep;
29 | private int mPosition, mMaxPosition;
30 |
31 | ProgressBarWrapper(ProgressBar progressBar, TextView text) {
32 | mProgressBar = progressBar;
33 | mProgressBar.setVisibility(View.GONE);
34 | mText = text;
35 | mText.setVisibility(View.GONE);
36 | mHandler = new Handler();
37 | mSteps = 10;
38 | }
39 |
40 | private void startProgressBar(final int max, final String text) {
41 | mHandler.post(new Runnable() {
42 | @Override
43 | public void run() {
44 | mProgressBar.setMax(max);
45 | mProgressBar.setProgress(0);
46 | mProgressBar.setVisibility(View.VISIBLE);
47 | if (text != null) {
48 | mText.setText(text);
49 | mText.setVisibility(View.VISIBLE);
50 | }
51 | }
52 | });
53 | }
54 |
55 | private void stepProgressBar(final int progress) {
56 | mHandler.post(new Runnable() {
57 | @Override
58 | public void run() {
59 | mProgressBar.setProgress(progress);
60 | }
61 | });
62 | }
63 |
64 | private void endProgressBar() {
65 | mHandler.post(new Runnable() {
66 | @Override
67 | public void run() {
68 | mProgressBar.setVisibility(View.GONE);
69 | mText.setVisibility(View.GONE);
70 | }
71 | });
72 | }
73 |
74 | void begin(int max, String text) {
75 | mLastStep = 0;
76 | mPosition = 0;
77 | mMaxPosition = max;
78 | startProgressBar(mSteps, text);
79 | }
80 |
81 | void step() {
82 | ++mPosition;
83 | int newStep = (mSteps * mPosition + mMaxPosition / 2) / mMaxPosition;
84 | if (newStep != mLastStep) {
85 | stepProgressBar(newStep);
86 | mLastStep = newStep;
87 | }
88 | }
89 |
90 | void end() {
91 | endProgressBar();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 选择照片
4 | 拍摄照片
5 | 保存为波形(.wav)文件
6 | 停止
7 | 播放
8 | 更改图片
9 | 旋转
10 | 重置
11 | 完成
12 | 编码模式
13 | 隐私政策
14 | 关于SSTV Encoder
15 | "\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 "
16 | 加载图像时出错
17 | 定位图像时出错
18 | 不受支持的内容
19 | 先前所使用的图像未能被加载
20 | 这个设备没有摄像头
21 | 发送…
22 | %1$s 另存为文件…
23 | 未能解析另一个活动
24 | 另一个活动未能被启动
25 | 发送邮件
26 | 好的
27 | SSTV Encoder - BUG反馈
28 | 发送BUG反馈
29 | 粗体
30 | 斜体
31 | 描边
32 | 颜色
33 | 描边颜色
34 | 大小
35 | 字体
36 | 默认字体
37 | 文本
38 | 小
39 | 常规
40 | 大
41 | 很大
42 | 描边大小
43 | 细
44 | 常规
45 | 粗
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ColorFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.app.AlertDialog;
19 | import android.app.Dialog;
20 | import android.graphics.Color;
21 | import android.os.Bundle;
22 | import androidx.annotation.NonNull;
23 | import androidx.fragment.app.DialogFragment;
24 | import android.view.View;
25 |
26 | import java.util.ArrayList;
27 | import java.util.List;
28 |
29 | import om.sstvencoder.ColorPalette.ColorPaletteView;
30 |
31 | public class ColorFragment extends DialogFragment
32 | implements ColorPaletteView.OnColorSelectedListener {
33 |
34 | public interface OnColorSelectedListener {
35 | void onColorSelected(DialogFragment fragment, int color);
36 |
37 | void onCancel(DialogFragment fragment);
38 | }
39 |
40 | private List mListeners;
41 | private int mTitle;
42 | private int mColor;
43 |
44 | public ColorFragment() {
45 | mListeners = new ArrayList<>();
46 | mTitle = R.string.color;
47 | mColor = Color.WHITE;
48 | }
49 |
50 | public void setTitle(int title) {
51 | mTitle = title;
52 | }
53 |
54 | public void setColor(int color) {
55 | mColor = color;
56 | }
57 |
58 | public void addOnColorSelectedListener(OnColorSelectedListener listener) {
59 | mListeners.add(listener);
60 | }
61 |
62 | @NonNull
63 | @Override
64 | public Dialog onCreateDialog(Bundle savedInstanceState) {
65 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
66 | View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_color, null);
67 | ColorPaletteView colorView = view.findViewById(R.id.select_color);
68 | colorView.setColor(mColor);
69 | colorView.addOnColorSelectedListener(this);
70 | builder.setTitle(mTitle);
71 | builder.setView(view);
72 | return builder.create();
73 | }
74 |
75 | @Override
76 | public void onColorChanged(View v, int color) {
77 | }
78 |
79 | @Override
80 | public void onColorSelected(View v, int color) {
81 | for (OnColorSelectedListener listener : mListeners)
82 | listener.onColorSelected(this, color);
83 | dismiss();
84 | }
85 |
86 | @Override
87 | public void onCancel(View v) {
88 | for (OnColorSelectedListener listener : mListeners)
89 | listener.onCancel(this);
90 | dismiss();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Wraase.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Color;
20 |
21 | import om.sstvencoder.ModeInterfaces.ModeSize;
22 | import om.sstvencoder.Output.IOutput;
23 |
24 | @ModeSize(width = 320, height = 256)
25 | @ModeDescription(name = Wraase.Name)
26 | class Wraase extends Mode {
27 | public static final String Name = "Wraase SC2 180";
28 |
29 | private final int mSyncPulseSamples;
30 | private final double mSyncPulseFrequency;
31 |
32 | private final int mPorchSamples;
33 | private final double mPorchFrequency;
34 |
35 | private final int mColorScanSamples;
36 |
37 | Wraase(Bitmap bitmap, IOutput output) {
38 | super(bitmap, output);
39 |
40 | mVISCode = 55;
41 | mColorScanSamples = convertMsToSamples(235.0);
42 |
43 | mSyncPulseSamples = convertMsToSamples(5.5225);
44 | mSyncPulseFrequency = 1200.0;
45 |
46 | mPorchSamples = convertMsToSamples(0.5);
47 | mPorchFrequency = 1500.0;
48 | }
49 |
50 | protected int getTransmissionSamples() {
51 | int lineSamples = mSyncPulseSamples + mPorchSamples + 3 * mColorScanSamples;
52 | return mBitmap.getHeight() * lineSamples;
53 | }
54 |
55 | protected void writeEncodedLine() {
56 | addSyncPulse();
57 | addPorch();
58 | addRedScan(mLine);
59 | addGreenScan(mLine);
60 | addBlueScan(mLine);
61 | }
62 |
63 | private void addSyncPulse() {
64 | for (int i = 0; i < mSyncPulseSamples; ++i)
65 | setTone(mSyncPulseFrequency);
66 | }
67 |
68 | private void addPorch() {
69 | for (int i = 0; i < mPorchSamples; ++i)
70 | setTone(mPorchFrequency);
71 | }
72 |
73 | private void addRedScan(int y) {
74 | for (int i = 0; i < mColorScanSamples; ++i)
75 | setColorTone(Color.red(getColor(i, y)));
76 | }
77 |
78 | private void addGreenScan(int y) {
79 | for (int i = 0; i < mColorScanSamples; ++i)
80 | setColorTone(Color.green(getColor(i, y)));
81 | }
82 |
83 | private void addBlueScan(int y) {
84 | for (int i = 0; i < mColorScanSamples; ++i)
85 | setColorTone(Color.blue(getColor(i, y)));
86 | }
87 |
88 | private int getColor(int colorScanSample, int y) {
89 | int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
90 | return mBitmap.getPixel(x, y);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/PD.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.Modes.ImageFormats.Yuv;
21 | import om.sstvencoder.Modes.ImageFormats.YuvFactory;
22 | import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
23 | import om.sstvencoder.Output.IOutput;
24 |
25 | abstract class PD extends Mode {
26 | private final Yuv mYuv;
27 |
28 | private final int mSyncPulseSamples;
29 | private final double mSyncPulseFrequency;
30 |
31 | private final int mPorchSamples;
32 | private final double mPorchFrequency;
33 |
34 | protected double mColorScanDurationMs;
35 | protected int mColorScanSamples;
36 |
37 | PD(Bitmap bitmap, IOutput output) {
38 | super(bitmap, output);
39 |
40 | mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.YUV440P);
41 |
42 | mSyncPulseSamples = convertMsToSamples(20.0);
43 | mSyncPulseFrequency = 1200.0;
44 |
45 | mPorchSamples = convertMsToSamples(2.08);
46 | mPorchFrequency = 1500.0;
47 | }
48 |
49 | protected int getTransmissionSamples() {
50 | int lineSamples = mSyncPulseSamples + mPorchSamples + 4 * mColorScanSamples;
51 | return mBitmap.getHeight() / 2 * lineSamples;
52 | }
53 |
54 | @Override
55 | public int getProcessCount() {
56 | return mBitmap.getHeight() / 2;
57 | }
58 |
59 | protected void writeEncodedLine() {
60 | addSyncPulse();
61 | addPorch();
62 | addYScan(mLine);
63 | addVScan(mLine);
64 | addUScan(mLine);
65 | addYScan(++mLine);
66 | }
67 |
68 | private void addSyncPulse() {
69 | for (int i = 0; i < mSyncPulseSamples; ++i)
70 | setTone(mSyncPulseFrequency);
71 | }
72 |
73 | private void addPorch() {
74 | for (int i = 0; i < mPorchSamples; ++i)
75 | setTone(mPorchFrequency);
76 | }
77 |
78 | private void addYScan(int y) {
79 | for (int i = 0; i < mColorScanSamples; ++i)
80 | setColorTone(mYuv.getY((i * mYuv.getWidth()) / mColorScanSamples, y));
81 | }
82 |
83 | private void addUScan(int y) {
84 | for (int i = 0; i < mColorScanSamples; ++i)
85 | setColorTone(mYuv.getU((i * mYuv.getWidth()) / mColorScanSamples, y));
86 | }
87 |
88 | private void addVScan(int y) {
89 | for (int i = 0; i < mColorScanSamples; ++i)
90 | setColorTone(mYuv.getV((i * mYuv.getWidth()) / mColorScanSamples, y));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/ModeFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import java.lang.reflect.Constructor;
21 |
22 | import om.sstvencoder.ModeInterfaces.IMode;
23 | import om.sstvencoder.ModeInterfaces.IModeInfo;
24 | import om.sstvencoder.ModeInterfaces.ModeSize;
25 | import om.sstvencoder.Output.IOutput;
26 |
27 | public final class ModeFactory {
28 | public static Class> getDefaultMode() {
29 | return Robot36.class;
30 | }
31 |
32 | public static String getDefaultModeClassName() {
33 | return (new ModeInfo(getDefaultMode())).getModeClassName();
34 | }
35 |
36 | public static IModeInfo[] getModeInfoList() {
37 | return new IModeInfo[]{
38 | new ModeInfo(Martin1.class), new ModeInfo(Martin2.class),
39 | new ModeInfo(PD50.class), new ModeInfo(PD90.class), new ModeInfo(PD120.class),
40 | new ModeInfo(PD160.class), new ModeInfo(PD180.class),
41 | new ModeInfo(PD240.class), new ModeInfo(PD290.class),
42 | new ModeInfo(Scottie1.class), new ModeInfo(Scottie2.class), new ModeInfo(ScottieDX.class),
43 | new ModeInfo(Robot36.class), new ModeInfo(Robot72.class),
44 | new ModeInfo(Wraase.class)
45 | };
46 | }
47 |
48 | public static IModeInfo getModeInfo(Class> modeClass) {
49 | if (!isModeClassValid(modeClass))
50 | return null;
51 |
52 | return new ModeInfo(modeClass);
53 | }
54 |
55 | public static IMode CreateMode(Class> modeClass, Bitmap bitmap, IOutput output) {
56 | Mode mode = null;
57 |
58 | if (bitmap != null && output != null && isModeClassValid(modeClass)) {
59 | ModeSize size = modeClass.getAnnotation(ModeSize.class);
60 |
61 | if (bitmap.getWidth() == size.width() && bitmap.getHeight() == size.height()) {
62 | try {
63 | Constructor constructor = modeClass.getDeclaredConstructor(Bitmap.class, IOutput.class);
64 | mode = (Mode) constructor.newInstance(bitmap, output);
65 | } catch (Exception ignore) {
66 | }
67 | }
68 | }
69 |
70 | return mode;
71 | }
72 |
73 | private static boolean isModeClassValid(Class> modeClass) {
74 | return Mode.class.isAssignableFrom(modeClass) &&
75 | modeClass.isAnnotationPresent(ModeSize.class) &&
76 | modeClass.isAnnotationPresent(ModeDescription.class);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SSTV Encoder
4 | Pick Picture
5 | Take Picture
6 | Save as WAVE File
7 | Stop
8 | Play
9 | Transform Image
10 | Rotate
11 | Reset
12 | Done
13 | Modes
14 | Privacy Policy
15 | About SSTV Encoder
16 |
17 | SSTV Encoder %1$s\nCopyright 2017 Olga Miller
18 | \n\nSSTV Encoder sends images via Slow Scan Television (SSTV).
19 | \n\nFor more info, see open source code: \nhttps://github.com/olgamiller/SSTVEncoder2
20 | \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.
21 |
22 | Image loading error
23 | Image orientation error
24 | Unsupported content.
25 | Previous image could not be loaded.
26 | Device has no camera.
27 | Sending…
28 | %1$s saving…
29 | Another activity could not be resolved.
30 | Another activity could not be started.
31 | Send Email
32 | OK
33 | SSTV Encoder - Bug Report
34 | Send Bug Report:
35 | Bold
36 | Italic
37 | Outline
38 | Color
39 | Color
40 | Size
41 | Font
42 | Default
43 | Text
44 | Small
45 | Normal
46 | Large
47 | Huge
48 | Size
49 | Thin
50 | Normal
51 | Thick
52 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Martin.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Color;
20 |
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | abstract class Martin extends Mode {
24 | private final int mSyncPulseSamples;
25 | private final double mSyncPulseFrequency;
26 |
27 | private final int mSyncPorchSamples;
28 | private final double mSyncPorchFrequency;
29 |
30 | private final int mSeparatorSamples;
31 | private final double mSeparatorFrequency;
32 |
33 | protected double mColorScanDurationMs;
34 | protected int mColorScanSamples;
35 |
36 | Martin(Bitmap bitmap, IOutput output) {
37 | super(bitmap, output);
38 |
39 | mSyncPulseSamples = convertMsToSamples(4.862);
40 | mSyncPulseFrequency = 1200.0;
41 |
42 | mSyncPorchSamples = convertMsToSamples(0.572);
43 | mSyncPorchFrequency = 1500.0;
44 |
45 | mSeparatorSamples = convertMsToSamples(0.572);
46 | mSeparatorFrequency = 1500.0;
47 | }
48 |
49 | protected int getTransmissionSamples() {
50 | int lineSamples = mSyncPulseSamples + mSyncPorchSamples
51 | + 3 * (mSeparatorSamples + mColorScanSamples);
52 | return mBitmap.getHeight() * lineSamples;
53 | }
54 |
55 | protected void writeEncodedLine() {
56 | addSyncPulse();
57 | addSyncPorch();
58 | addGreenScan(mLine);
59 | addSeparator();
60 | addBlueScan(mLine);
61 | addSeparator();
62 | addRedScan(mLine);
63 | addSeparator();
64 | }
65 |
66 | private void addSyncPulse() {
67 | for (int i = 0; i < mSyncPulseSamples; ++i)
68 | setTone(mSyncPulseFrequency);
69 | }
70 |
71 | private void addSyncPorch() {
72 | for (int i = 0; i < mSyncPorchSamples; ++i)
73 | setTone(mSyncPorchFrequency);
74 | }
75 |
76 | private void addSeparator() {
77 | for (int i = 0; i < mSeparatorSamples; ++i)
78 | setTone(mSeparatorFrequency);
79 | }
80 |
81 | private void addGreenScan(int y) {
82 | for (int i = 0; i < mColorScanSamples; ++i)
83 | setColorTone(Color.green(getColor(i, y)));
84 | }
85 |
86 | private void addBlueScan(int y) {
87 | for (int i = 0; i < mColorScanSamples; ++i)
88 | setColorTone(Color.blue(getColor(i, y)));
89 | }
90 |
91 | private void addRedScan(int y) {
92 | for (int i = 0; i < mColorScanSamples; ++i)
93 | setColorTone(Color.red(getColor(i, y)));
94 | }
95 |
96 | private int getColor(int colorScanSample, int y) {
97 | int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
98 | return mBitmap.getPixel(x, y);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Scottie.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Color;
20 |
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | abstract class Scottie extends Mode {
24 | private final int mSyncPulseSamples;
25 | private final double mSyncPulseFrequency;
26 |
27 | private final int mSyncPorchSamples;
28 | private final double mSyncPorchFrequency;
29 |
30 | private final int mSeparatorSamples;
31 | private final double mSeparatorFrequency;
32 |
33 | protected double mColorScanDurationMs;
34 | protected int mColorScanSamples;
35 |
36 | Scottie(Bitmap bitmap, IOutput output) {
37 | super(bitmap, output);
38 |
39 | mSyncPulseSamples = convertMsToSamples(9.0);
40 | mSyncPulseFrequency = 1200.0;
41 |
42 | mSyncPorchSamples = convertMsToSamples(1.5);
43 | mSyncPorchFrequency = 1500.0;
44 |
45 | mSeparatorSamples = convertMsToSamples(1.5);
46 | mSeparatorFrequency = 1500.0;
47 | }
48 |
49 | protected int getTransmissionSamples() {
50 | int lineSamples = 2 * mSeparatorSamples + 3 * mColorScanSamples +
51 | mSyncPulseSamples + mSyncPorchSamples;
52 | return mSyncPulseSamples + mBitmap.getHeight() * lineSamples;
53 | }
54 |
55 | protected void writeEncodedLine() {
56 | if (mLine == 0)
57 | addSyncPulse();
58 |
59 | addSeparator();
60 | addGreenScan(mLine);
61 | addSeparator();
62 | addBlueScan(mLine);
63 | addSyncPulse();
64 | addSyncPorch();
65 | addRedScan(mLine);
66 | }
67 |
68 | private void addSyncPulse() {
69 | for (int i = 0; i < mSyncPulseSamples; ++i)
70 | setTone(mSyncPulseFrequency);
71 | }
72 |
73 | private void addSyncPorch() {
74 | for (int i = 0; i < mSyncPorchSamples; ++i)
75 | setTone(mSyncPorchFrequency);
76 | }
77 |
78 | private void addSeparator() {
79 | for (int i = 0; i < mSeparatorSamples; ++i)
80 | setTone(mSeparatorFrequency);
81 | }
82 |
83 | private void addGreenScan(int y) {
84 | for (int i = 0; i < mColorScanSamples; ++i)
85 | setColorTone(Color.green(getColor(i, y)));
86 | }
87 |
88 | private void addBlueScan(int y) {
89 | for (int i = 0; i < mColorScanSamples; ++i)
90 | setColorTone(Color.blue(getColor(i, y)));
91 | }
92 |
93 | private void addRedScan(int y) {
94 | for (int i = 0; i < mColorScanSamples; ++i)
95 | setColorTone(Color.red(getColor(i, y)));
96 | }
97 |
98 | private int getColor(int colorScanSample, int y) {
99 | int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
100 | return mBitmap.getPixel(x, y);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/Label.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | import android.graphics.Color;
19 |
20 | import java.io.Serializable;
21 | import java.lang.reflect.Field;
22 | import java.lang.reflect.Modifier;
23 |
24 | public class Label implements Serializable {
25 | public static final float TEXT_SIZE_NORMAL = 2f;
26 | public static final float OUTLINE_SIZE_NORMAL = 0.05f;
27 | private String mText;
28 | private float mTextSize, mOutlineSize;
29 | private String mFamilyName;
30 | private boolean mBold, mItalic, mOutline;
31 | private int mForeColor, mBackColor, mOutlineColor;
32 |
33 | public Label() {
34 | mText = "";
35 | mTextSize = TEXT_SIZE_NORMAL;
36 | mFamilyName = null;
37 | mBold = true;
38 | mItalic = false;
39 | mForeColor = Color.BLACK;
40 | mBackColor = Color.TRANSPARENT;
41 | mOutline = true;
42 | mOutlineSize = OUTLINE_SIZE_NORMAL;
43 | mOutlineColor = Color.WHITE;
44 | }
45 |
46 | public String getText() {
47 | return mText;
48 | }
49 |
50 | public void setText(String text) {
51 | if (text != null)
52 | mText = text;
53 | }
54 |
55 | public float getTextSize() {
56 | return mTextSize;
57 | }
58 |
59 | public void setTextSize(float size) {
60 | if (size > 0f)
61 | mTextSize = size;
62 | }
63 |
64 | public String getFamilyName() {
65 | return mFamilyName;
66 | }
67 |
68 | public void setFamilyName(String familyName) {
69 | mFamilyName = familyName;
70 | }
71 |
72 | public boolean getBold() {
73 | return mBold;
74 | }
75 |
76 | public void setBold(boolean bold) {
77 | mBold = bold;
78 | }
79 |
80 | public boolean getItalic() {
81 | return mItalic;
82 | }
83 |
84 | public void setItalic(boolean italic) {
85 | mItalic = italic;
86 | }
87 |
88 | public int getForeColor() {
89 | return mForeColor;
90 | }
91 |
92 | public void setForeColor(int color) {
93 | mForeColor = color;
94 | }
95 |
96 | public int getBackColor() {
97 | return mBackColor;
98 | }
99 |
100 | public void setBackColor(int color) {
101 | mBackColor = color;
102 | }
103 |
104 | public boolean getOutline() {
105 | return mOutline;
106 | }
107 |
108 | public void setOutline(boolean outline) {
109 | mOutline = outline;
110 | }
111 |
112 | public float getOutlineSize() {
113 | return mOutlineSize;
114 | }
115 |
116 | public void setOutlineSize(float size) {
117 | mOutlineSize = size;
118 | }
119 |
120 | public int getOutlineColor() {
121 | return mOutlineColor;
122 | }
123 |
124 | public void setOutlineColor(int color) {
125 | mOutlineColor = color;
126 | }
127 |
128 | public Label getClone() {
129 | Label clone = new Label();
130 | try {
131 | for (Field field : getClass().getDeclaredFields()) {
132 | if (!Modifier.isFinal(field.getModifiers()))
133 | field.set(clone, field.get(this));
134 | }
135 | } catch (Exception ignore) {
136 | }
137 | return clone;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Output/WaveFileOutputContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Output;
17 |
18 | import android.content.ContentResolver;
19 | import android.content.ContentValues;
20 | import android.net.Uri;
21 | import android.os.Build;
22 | import android.os.Environment;
23 | import android.provider.MediaStore;
24 |
25 | import java.io.File;
26 | import java.io.FileOutputStream;
27 | import java.io.OutputStream;
28 |
29 | public class WaveFileOutputContext {
30 | private ContentResolver mContentResolver;
31 | private String mFileName;
32 | private File mFile;
33 | private Uri mUri;
34 | private ContentValues mValues;
35 |
36 | public WaveFileOutputContext(ContentResolver contentResolver, String fileName) {
37 | mContentResolver = contentResolver;
38 | mFileName = fileName;
39 | mValues = getContentValues(fileName);
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
41 | mUri = mContentResolver.insert(MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), mValues);
42 | else
43 | mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), mFileName);
44 | }
45 |
46 | private ContentValues getContentValues(String fileName) {
47 | ContentValues values = new ContentValues();
48 | values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav");
49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
50 | values.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName);
51 | values.put(MediaStore.Audio.Media.RELATIVE_PATH, (new File(Environment.DIRECTORY_MUSIC, "SSTV Encoder")).getPath());
52 | values.put(MediaStore.Audio.Media.IS_PENDING, 1);
53 | } else {
54 | values.put(MediaStore.Audio.Media.ALBUM, "SSTV Encoder");
55 | values.put(MediaStore.Audio.Media.TITLE, fileName);
56 | values.put(MediaStore.Audio.Media.IS_MUSIC, true);
57 | }
58 | return values;
59 | }
60 |
61 | public String getFileName() {
62 | return mFileName;
63 | }
64 |
65 | public OutputStream getOutputStream() {
66 | try {
67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
68 | return mContentResolver.openOutputStream(mUri);
69 | } else
70 | return new FileOutputStream(mFile);
71 | } catch (Exception ignore) {
72 | }
73 | return null;
74 | }
75 |
76 | public void update() {
77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
78 | if (mUri != null && mValues != null) {
79 | mValues.clear();
80 | mValues.put(MediaStore.Audio.Media.IS_PENDING, 0);
81 | mContentResolver.update(mUri, mValues, null, null);
82 | }
83 | } else {
84 | if (mFile != null && mValues != null) {
85 | mValues.put(MediaStore.Audio.Media.DATA, mFile.toString());
86 | mUri = mContentResolver.insert(MediaStore.Audio.Media.getContentUriForPath(mFile.getAbsolutePath()), mValues);
87 | }
88 | }
89 | }
90 |
91 | public void deleteFile() {
92 | try {
93 | if (mFile == null)
94 | mFile = new File(mUri.getPath());
95 | mFile.delete();
96 | } catch (Exception ignore) {
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ColorPalette/ColorPaletteView.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ColorPalette;
17 |
18 | import android.content.Context;
19 | import android.graphics.Canvas;
20 | import android.graphics.Color;
21 | import android.graphics.PorterDuff;
22 | import androidx.annotation.NonNull;
23 | import android.util.AttributeSet;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 |
27 | import java.util.ArrayList;
28 |
29 | public class ColorPaletteView extends View {
30 |
31 | public interface OnColorSelectedListener {
32 | void onColorChanged(View v, int color);
33 |
34 | void onColorSelected(View v, int color);
35 |
36 | void onCancel(View v);
37 | }
38 |
39 | private final ArrayList mListeners;
40 | private final IColorPalette mPalette;
41 |
42 | public ColorPaletteView(Context context, AttributeSet attrs) {
43 | super(context, attrs);
44 | mListeners = new ArrayList<>();
45 | mPalette = new GridColorPalette(GridColorPalette.getStandardColors(),
46 | getResources().getDisplayMetrics().density);
47 | }
48 |
49 | public int getColor() {
50 | return mPalette.getSelectedColor();
51 | }
52 |
53 | public void setColor(int color) {
54 | mPalette.selectColor(color);
55 | }
56 |
57 | @Override
58 | protected void onSizeChanged(int w, int h, int old_w, int old_h) {
59 | super.onSizeChanged(w, h, old_w, old_h);
60 | mPalette.updateSize(w, h);
61 | }
62 |
63 | @Override
64 | protected void onDraw(@NonNull Canvas canvas) {
65 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
66 | mPalette.draw(canvas);
67 | }
68 |
69 | @Override
70 | public boolean onTouchEvent(@NonNull MotionEvent e) {
71 | boolean consumed = false;
72 | switch (e.getAction()) {
73 | case MotionEvent.ACTION_DOWN:
74 | case MotionEvent.ACTION_MOVE: {
75 | update(e.getX(), e.getY());
76 | consumed = true;
77 | break;
78 | }
79 | case MotionEvent.ACTION_UP: {
80 | float x = e.getX();
81 | float y = e.getY();
82 | if (getLeft() <= x && x <= getRight() && getTop() <= y && y <= getBottom())
83 | colorSelectedCallback();
84 | else
85 | cancelCallback();
86 | consumed = true;
87 | break;
88 | }
89 | }
90 | return consumed || super.onTouchEvent(e);
91 | }
92 |
93 | private void update(float x, float y) {
94 | if (mPalette.selectColor(x, y)) {
95 | invalidate();
96 | colorChangedCallback();
97 | }
98 | }
99 |
100 | public void addOnColorSelectedListener(OnColorSelectedListener listener) {
101 | mListeners.add(listener);
102 | }
103 |
104 | private void colorChangedCallback() {
105 | for (OnColorSelectedListener listener : mListeners) {
106 | listener.onColorChanged(this, mPalette.getSelectedColor());
107 | }
108 | }
109 |
110 | private void colorSelectedCallback() {
111 | for (OnColorSelectedListener listener : mListeners) {
112 | listener.onColorSelected(this, mPalette.getSelectedColor());
113 | }
114 | }
115 |
116 | private void cancelCallback() {
117 | for (OnColorSelectedListener listener : mListeners) {
118 | listener.onCancel(this);
119 | }
120 | }
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/LabelContainer.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Rect;
20 | import androidx.annotation.NonNull;
21 |
22 | import java.io.IOException;
23 |
24 | class LabelContainer {
25 | private Label mLabel;
26 | private LabelPainter mPainter;
27 | private Position mPosition; // left-bottom corner
28 |
29 | LabelContainer(@NonNull Label label) {
30 | mLabel = label;
31 | mPainter = new LabelPainter(label);
32 | mPosition = new Position();
33 | }
34 |
35 | boolean contains(float x, float y) {
36 | return mPainter.getBounds().contains(x, y);
37 | }
38 |
39 | void draw(Canvas canvas) {
40 | mPainter.draw(canvas);
41 | }
42 |
43 | void drawActive(Canvas canvas) {
44 | mPainter.drawActive(canvas);
45 | }
46 |
47 | void draw(Canvas canvas, Rect src, Rect dst) {
48 | mPainter.draw(canvas, src, dst);
49 | }
50 |
51 | void jumpInside(float textSizeFactor, float screenW, float screenH) {
52 | mPainter.moveLabelInside(textSizeFactor, screenW, screenH, mPosition);
53 | }
54 |
55 | void offset(float x, float y) {
56 | mPosition.offset(x, y);
57 | }
58 |
59 | void update(float textSizeFactor, float screenW, float screenH) {
60 | mPainter.update(textSizeFactor, screenW, screenH, mPosition);
61 | }
62 |
63 | Label getContent() {
64 | return mLabel;
65 | }
66 |
67 | void setContent(@NonNull Label label) {
68 | mLabel = label;
69 | mPainter.setLabel(label);
70 | }
71 |
72 | void write(IWriter writer) throws IOException {
73 | writer.beginRootObject();
74 | {
75 | writer.write("position_x", mPosition.getX());
76 | writer.write("position_y", mPosition.getY());
77 | writer.beginObject("label");
78 | {
79 | writeLabel(writer, mLabel);
80 | }
81 | writer.endObject();
82 | }
83 | writer.endObject();
84 | }
85 |
86 | void read(IReader reader) throws IOException {
87 | reader.beginRootObject();
88 | {
89 | mPosition.set(reader.readFloat(), reader.readFloat());
90 | reader.beginObject();
91 | {
92 | readLabel(reader, mLabel);
93 | }
94 | reader.endObject();
95 | }
96 | reader.endObject();
97 | }
98 |
99 | private void writeLabel(IWriter writer, Label label) throws IOException {
100 | writer.write("text", label.getText());
101 | writer.write("text_size", label.getTextSize());
102 | writer.write("family_name", label.getFamilyName());
103 | writer.write("bold", label.getBold());
104 | writer.write("italic", label.getItalic());
105 | writer.write("fore_color", label.getForeColor());
106 | writer.write("back_color", label.getBackColor());
107 | writer.write("outline", label.getOutline());
108 | writer.write("outline_size", label.getOutlineSize());
109 | writer.write("outline_color", label.getOutlineColor());
110 | }
111 |
112 | private void readLabel(IReader reader, Label label) throws IOException {
113 | label.setText(reader.readString());
114 | label.setTextSize(reader.readFloat());
115 | label.setFamilyName(reader.readString());
116 | label.setBold(reader.readBoolean());
117 | label.setItalic(reader.readBoolean());
118 | label.setForeColor(reader.readInt());
119 | label.setBackColor(reader.readInt());
120 | label.setOutline(reader.readBoolean());
121 | label.setOutlineSize(reader.readFloat());
122 | label.setOutlineColor(reader.readInt());
123 | }
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Robot72.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.Modes.ImageFormats.Yuv;
21 | import om.sstvencoder.Modes.ImageFormats.YuvFactory;
22 | import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
23 | import om.sstvencoder.ModeInterfaces.ModeSize;
24 | import om.sstvencoder.Output.IOutput;
25 |
26 | @ModeSize(width = 320, height = 240)
27 | @ModeDescription(name = Robot72.Name)
28 | class Robot72 extends Mode {
29 | public static final String Name = "Robot 72";
30 |
31 | private final Yuv mYuv;
32 |
33 | private final int mLumaScanSamples;
34 | private final int mChrominanceScanSamples;
35 |
36 | private final int mSyncPulseSamples;
37 | private final double mSyncPulseFrequency;
38 |
39 | private final int mSyncPorchSamples;
40 | private final double mSyncPorchFrequency;
41 |
42 | private final int mPorchSamples;
43 | private final double mPorchFrequency;
44 |
45 | private final int mSeparatorSamples;
46 | private final double mFirstSeparatorFrequency;
47 | private final double mSecondSeparatorFrequency;
48 |
49 | Robot72(Bitmap bitmap, IOutput output) {
50 | super(bitmap, output);
51 |
52 | mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.YUY2);
53 | mVISCode = 12;
54 |
55 | mLumaScanSamples = convertMsToSamples(138.0);
56 | mChrominanceScanSamples = convertMsToSamples(69.0);
57 |
58 | mSyncPulseSamples = convertMsToSamples(9.0);
59 | mSyncPulseFrequency = 1200.0;
60 |
61 | mSyncPorchSamples = convertMsToSamples(3.0);
62 | mSyncPorchFrequency = 1500.0;
63 |
64 | mPorchSamples = convertMsToSamples(1.5);
65 | mPorchFrequency = 1900.0;
66 |
67 | mSeparatorSamples = convertMsToSamples(4.5);
68 | mFirstSeparatorFrequency = 1500.0;
69 | mSecondSeparatorFrequency = 2300.0;
70 | }
71 |
72 | protected int getTransmissionSamples() {
73 | int lineSamples = mSyncPulseSamples + mSyncPorchSamples + mLumaScanSamples
74 | + 2 * (mSeparatorSamples + mPorchSamples + mChrominanceScanSamples);
75 | return mBitmap.getHeight() * lineSamples;
76 | }
77 |
78 | protected void writeEncodedLine() {
79 | addSyncPulse();
80 | addSyncPorch();
81 | addYScan(mLine);
82 | addSeparator(mFirstSeparatorFrequency);
83 | addPorch();
84 | addVScan(mLine);
85 | addSeparator(mSecondSeparatorFrequency);
86 | addPorch();
87 | addUScan(mLine);
88 | }
89 |
90 | private void addSyncPulse() {
91 | for (int i = 0; i < mSyncPulseSamples; ++i)
92 | setTone(mSyncPulseFrequency);
93 | }
94 |
95 | private void addSyncPorch() {
96 | for (int i = 0; i < mSyncPorchSamples; ++i)
97 | setTone(mSyncPorchFrequency);
98 | }
99 |
100 | private void addSeparator(double separatorFrequency) {
101 | for (int i = 0; i < mSeparatorSamples; ++i)
102 | setTone(separatorFrequency);
103 | }
104 |
105 | private void addPorch() {
106 | for (int i = 0; i < mPorchSamples; ++i)
107 | setTone(mPorchFrequency);
108 | }
109 |
110 | private void addYScan(int y) {
111 | for (int i = 0; i < mLumaScanSamples; ++i)
112 | setColorTone(mYuv.getY((i * mYuv.getWidth()) / mLumaScanSamples, y));
113 | }
114 |
115 | private void addUScan(int y) {
116 | for (int i = 0; i < mChrominanceScanSamples; ++i)
117 | setColorTone(mYuv.getU((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
118 | }
119 |
120 | private void addVScan(int y) {
121 | for (int i = 0; i < mChrominanceScanSamples; ++i)
122 | setColorTone(mYuv.getV((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Robot36.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.Modes.ImageFormats.Yuv;
21 | import om.sstvencoder.Modes.ImageFormats.YuvFactory;
22 | import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
23 | import om.sstvencoder.ModeInterfaces.ModeSize;
24 | import om.sstvencoder.Output.IOutput;
25 |
26 | @ModeSize(width = 320, height = 240)
27 | @ModeDescription(name = Robot36.Name)
28 | class Robot36 extends Mode {
29 | public static final String Name = "Robot 36";
30 |
31 | private final Yuv mYuv;
32 |
33 | private final int mLumaScanSamples;
34 | private final int mChrominanceScanSamples;
35 |
36 | private final int mSyncPulseSamples;
37 | private final double mSyncPulseFrequency;
38 |
39 | private final int mSyncPorchSamples;
40 | private final double mSyncPorchFrequency;
41 |
42 | private final int mPorchSamples;
43 | private final double mPorchFrequency;
44 |
45 | private final int mSeparatorSamples;
46 | private final double mEvenSeparatorFrequency;
47 | private final double mOddSeparatorFrequency;
48 |
49 | Robot36(Bitmap bitmap, IOutput output) {
50 | super(bitmap, output);
51 |
52 | mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.NV21);
53 | mVISCode = 8;
54 |
55 | mLumaScanSamples = convertMsToSamples(88.0);
56 | mChrominanceScanSamples = convertMsToSamples(44.0);
57 |
58 | mSyncPulseSamples = convertMsToSamples(9.0);
59 | mSyncPulseFrequency = 1200.0;
60 |
61 | mSyncPorchSamples = convertMsToSamples(3.0);
62 | mSyncPorchFrequency = 1500.0;
63 |
64 | mPorchSamples = convertMsToSamples(1.5);
65 | mPorchFrequency = 1900.0;
66 |
67 | mSeparatorSamples = convertMsToSamples(4.5);
68 | mEvenSeparatorFrequency = 1500.0;
69 | mOddSeparatorFrequency = 2300.0;
70 | }
71 |
72 | protected int getTransmissionSamples() {
73 | int lineSamples = mSyncPulseSamples + mSyncPorchSamples
74 | + mLumaScanSamples + mSeparatorSamples
75 | + mPorchSamples + mChrominanceScanSamples;
76 | return mBitmap.getHeight() * lineSamples;
77 | }
78 |
79 | protected void writeEncodedLine() {
80 | addSyncPulse();
81 | addSyncPorch();
82 | addYScan(mLine);
83 |
84 | if (mLine % 2 == 0) {
85 | addSeparator(mEvenSeparatorFrequency);
86 | addPorch();
87 | addVScan(mLine);
88 | } else {
89 | addSeparator(mOddSeparatorFrequency);
90 | addPorch();
91 | addUScan(mLine);
92 | }
93 | }
94 |
95 | private void addSyncPulse() {
96 | for (int i = 0; i < mSyncPulseSamples; ++i)
97 | setTone(mSyncPulseFrequency);
98 | }
99 |
100 | private void addSyncPorch() {
101 | for (int i = 0; i < mSyncPorchSamples; ++i)
102 | setTone(mSyncPorchFrequency);
103 | }
104 |
105 | private void addSeparator(double separatorFrequency) {
106 | for (int i = 0; i < mSeparatorSamples; ++i)
107 | setTone(separatorFrequency);
108 | }
109 |
110 | private void addPorch() {
111 | for (int i = 0; i < mPorchSamples; ++i)
112 | setTone(mPorchFrequency);
113 | }
114 |
115 | private void addYScan(int y) {
116 | for (int i = 0; i < mLumaScanSamples; ++i)
117 | setColorTone(mYuv.getY((i * mYuv.getWidth()) / mLumaScanSamples, y));
118 | }
119 |
120 | private void addUScan(int y) {
121 | for (int i = 0; i < mChrominanceScanSamples; ++i)
122 | setColorTone(mYuv.getU((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
123 | }
124 |
125 | private void addVScan(int y) {
126 | for (int i = 0; i < mChrominanceScanSamples; ++i)
127 | setColorTone(mYuv.getV((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
128 | }
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Modes/Mode.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Modes;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import om.sstvencoder.ModeInterfaces.IMode;
21 | import om.sstvencoder.Output.IOutput;
22 |
23 | abstract class Mode implements IMode {
24 | protected Bitmap mBitmap;
25 | protected int mVISCode;
26 | protected int mLine;
27 | private IOutput mOutput;
28 | private double mSampleRate;
29 | private double mRunningIntegral;
30 |
31 | protected Mode(Bitmap bitmap, IOutput output) {
32 | mOutput = output;
33 | mSampleRate = mOutput.getSampleRate();
34 | mBitmap = bitmap;
35 | }
36 |
37 | @Override
38 | public void init() {
39 | mRunningIntegral = 0.0;
40 | mLine = 0;
41 | mOutput.init(getTotalSamples());
42 | writeCalibrationHeader();
43 | }
44 |
45 | @Override
46 | public int getProcessCount() {
47 | return mBitmap.getHeight();
48 | }
49 |
50 | @Override
51 | public boolean process() {
52 | if (mLine >= mBitmap.getHeight())
53 | return false;
54 |
55 | writeEncodedLine();
56 | ++mLine;
57 | return true;
58 | }
59 |
60 | // Note that also Bitmap will be recycled here
61 | @Override
62 | public void finish(boolean cancel) {
63 | mOutput.finish(cancel);
64 | destroyBitmap();
65 | }
66 |
67 | private int getTotalSamples() {
68 | return getHeaderSamples() + getTransmissionSamples();
69 | }
70 |
71 | private int getHeaderSamples() {
72 | return 2 * convertMsToSamples(300.0)
73 | + convertMsToSamples(10.0)
74 | + 10 * convertMsToSamples(30.0);
75 | }
76 |
77 | protected abstract int getTransmissionSamples();
78 |
79 | private void writeCalibrationHeader() {
80 | int leaderToneSamples = convertMsToSamples(300.0);
81 | double leaderToneFrequency = 1900.0;
82 |
83 | int breakSamples = convertMsToSamples(10.0);
84 | double breakFrequency = 1200.0;
85 |
86 | int visBitSamples = convertMsToSamples(30.0);
87 | double visBitSSFrequency = 1200.0;
88 | double[] visBitFrequency = new double[]{1300.0, 1100.0};
89 |
90 | for (int i = 0; i < leaderToneSamples; ++i)
91 | setTone(leaderToneFrequency);
92 |
93 | for (int i = 0; i < breakSamples; ++i)
94 | setTone(breakFrequency);
95 |
96 | for (int i = 0; i < leaderToneSamples; ++i)
97 | setTone(leaderToneFrequency);
98 |
99 | for (int i = 0; i < visBitSamples; ++i)
100 | setTone(visBitSSFrequency);
101 |
102 | int parity = 0;
103 | for (int pos = 0; pos < 7; ++pos) {
104 | int bit = (mVISCode >> pos) & 1;
105 | parity ^= bit;
106 | for (int i = 0; i < visBitSamples; ++i)
107 | setTone(visBitFrequency[bit]);
108 | }
109 |
110 | for (int i = 0; i < visBitSamples; ++i)
111 | setTone(visBitFrequency[parity]);
112 |
113 | for (int i = 0; i < visBitSamples; ++i)
114 | setTone(visBitSSFrequency);
115 | }
116 |
117 | protected abstract void writeEncodedLine();
118 |
119 | protected int convertMsToSamples(double durationMs) {
120 | return (int) Math.round(durationMs * mSampleRate / 1000.0);
121 | }
122 |
123 | protected void setTone(double frequency) {
124 | mRunningIntegral += 2.0 * frequency * Math.PI / mSampleRate;
125 | mRunningIntegral %= 2.0 * Math.PI;
126 | mOutput.write(Math.sin(mRunningIntegral));
127 | }
128 |
129 | protected void setColorTone(int color) {
130 | double blackFrequency = 1500.0;
131 | double whiteFrequency = 2300.0;
132 | setTone(color * (whiteFrequency - blackFrequency) / 255.0 + blackFrequency);
133 | }
134 |
135 | private void destroyBitmap() {
136 | if (mBitmap != null && !mBitmap.isRecycled()) {
137 | mBitmap.recycle();
138 | mBitmap = null;
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.Output;
17 |
18 | import java.io.BufferedOutputStream;
19 |
20 | class WaveFileOutput implements IOutput {
21 | private final double mSampleRate;
22 | private WaveFileOutputContext mContext;
23 | private BufferedOutputStream mOutputStream;
24 | private int mSamples, mWrittenSamples;
25 |
26 | WaveFileOutput(WaveFileOutputContext context, double sampleRate) {
27 | mContext = context;
28 | mSampleRate = sampleRate;
29 | }
30 |
31 | public void init(int samples) {
32 | int offset = (int) ((0.01 * mSampleRate) / 2.0);
33 | mSamples = samples + 2 * offset;
34 | mWrittenSamples = 0;
35 | InitOutputStream();
36 | writeHeader();
37 | padWithZeros(offset);
38 | }
39 |
40 | private void writeHeader() {
41 | try {
42 | int numChannels = 1; // mono
43 | int bitsPerSample = Short.SIZE;
44 | int blockAlign = numChannels * bitsPerSample / Byte.SIZE;
45 | int subchunk2Size = mSamples * blockAlign;
46 |
47 | mOutputStream.write("RIFF".getBytes()); // ChunkID
48 | mOutputStream.write(toLittleEndian(36 + subchunk2Size)); // ChunkSize
49 | mOutputStream.write("WAVE".getBytes()); // Format
50 |
51 | mOutputStream.write("fmt ".getBytes()); // Subchunk1ID
52 | mOutputStream.write(toLittleEndian(16)); // Subchunk1Size
53 | mOutputStream.write(toLittleEndian((short) 1)); // AudioFormat
54 | mOutputStream.write(toLittleEndian((short) numChannels)); // NumChannels
55 | mOutputStream.write(toLittleEndian((int) mSampleRate)); // SampleRate
56 | mOutputStream.write(toLittleEndian((int) mSampleRate * blockAlign)); // ByteRate
57 | mOutputStream.write(toLittleEndian((short) blockAlign)); // BlockAlign
58 | mOutputStream.write(toLittleEndian((short) bitsPerSample)); // BitsPerSample
59 |
60 | mOutputStream.write("data".getBytes()); // Subchunk2ID
61 | mOutputStream.write(toLittleEndian(subchunk2Size)); // Subchunk2Size
62 | } catch (Exception ignore) {
63 | }
64 | }
65 |
66 | private void InitOutputStream() {
67 | try {
68 | mOutputStream = new BufferedOutputStream(mContext.getOutputStream());
69 | } catch (Exception ignore) {
70 | }
71 | }
72 |
73 | @Override
74 | public double getSampleRate() {
75 | return mSampleRate;
76 | }
77 |
78 | @Override
79 | public void write(double value) {
80 | short tmp = (short) (value * Short.MAX_VALUE);
81 | ++mWrittenSamples;
82 | try {
83 | mOutputStream.write(toLittleEndian(tmp));
84 | } catch (Exception ignore) {
85 | }
86 | }
87 |
88 | @Override
89 | public void finish(boolean cancel) {
90 | if (!cancel)
91 | padWithZeros(mSamples);
92 |
93 | try {
94 | mOutputStream.close();
95 | mOutputStream = null;
96 | } catch (Exception ignore) {
97 | }
98 |
99 | if (cancel)
100 | mContext.deleteFile();
101 | }
102 |
103 | private void padWithZeros(int count) {
104 | try {
105 | while (mWrittenSamples++ < count)
106 | mOutputStream.write(toLittleEndian((short) 0));
107 | } catch (Exception ignore) {
108 | }
109 | }
110 |
111 | private byte[] toLittleEndian(int value) {
112 | byte[] buffer = new byte[4];
113 | buffer[0] = (byte) (value & 255);
114 | buffer[1] = (byte) ((value >> 8) & 255);
115 | buffer[2] = (byte) ((value >> 16) & 255);
116 | buffer[3] = (byte) ((value >> 24) & 255);
117 | return buffer;
118 | }
119 |
120 | private byte[] toLittleEndian(short value) {
121 | byte[] buffer = new byte[2];
122 | buffer[0] = (byte) (value & 255);
123 | buffer[1] = (byte) ((value >> 8) & 255);
124 | return buffer;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_edit_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
16 |
17 |
19 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
63 |
64 |
68 |
69 |
77 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
88 |
89 |
91 |
92 |
95 |
96 |
99 |
100 |
101 |
102 |
103 |
104 |
106 |
107 |
111 |
112 |
121 |
122 |
123 |
124 |
126 |
127 |
130 |
131 |
135 |
136 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Encoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | import java.util.LinkedList;
21 | import java.util.List;
22 |
23 | import om.sstvencoder.ModeInterfaces.IMode;
24 | import om.sstvencoder.ModeInterfaces.IModeInfo;
25 | import om.sstvencoder.Modes.ModeFactory;
26 | import om.sstvencoder.Output.IOutput;
27 | import om.sstvencoder.Output.OutputFactory;
28 | import om.sstvencoder.Output.WaveFileOutputContext;
29 |
30 | // Creates IMode instance
31 | class Encoder {
32 | private final MainActivityMessenger mMessenger;
33 | private final Thread mThread;
34 | private Thread mSaveWaveThread;
35 | private final List mQueue;
36 | private final ProgressBarWrapper mProgressBar, mProgressBar2;
37 | private boolean mQuit, mStop;
38 | private Class> mModeClass;
39 |
40 | Encoder(MainActivityMessenger messenger,
41 | ProgressBarWrapper progressBar, ProgressBarWrapper progressBar2) {
42 | mMessenger = messenger;
43 | mProgressBar = progressBar;
44 | mProgressBar2 = progressBar2;
45 | mQueue = new LinkedList<>();
46 | mQuit = false;
47 | mStop = false;
48 | mModeClass = ModeFactory.getDefaultMode();
49 |
50 | mThread = new Thread() {
51 | @Override
52 | public void run() {
53 | while (true) {
54 | IMode mode;
55 | synchronized (this) {
56 | while (mQueue.isEmpty() && !mQuit) {
57 | try {
58 | wait();
59 | } catch (Exception ignore) {
60 | }
61 | }
62 | if (mQuit)
63 | return;
64 |
65 | mStop = false;
66 | mode = mQueue.remove(0);
67 | }
68 | mode.init();
69 | mProgressBar.begin(mode.getProcessCount(),
70 | mMessenger.getString(R.string.progressbar_message_sending));
71 |
72 | while (mode.process()) {
73 | mProgressBar.step();
74 |
75 | synchronized (this) {
76 | if (mQuit || mStop)
77 | break;
78 | }
79 | }
80 | mode.finish(mStop);
81 | mProgressBar.end();
82 | }
83 | }
84 | };
85 | mThread.start();
86 | }
87 |
88 | boolean setMode(String className) {
89 | try {
90 | mModeClass = Class.forName(className);
91 | } catch (Exception ignore) {
92 | return false;
93 | }
94 | return true;
95 | }
96 |
97 | IModeInfo getModeInfo() {
98 | return ModeFactory.getModeInfo(mModeClass);
99 | }
100 |
101 | IModeInfo[] getModeInfoList() {
102 | return ModeFactory.getModeInfoList();
103 | }
104 |
105 | void play(Bitmap bitmap) {
106 | IOutput output = OutputFactory.createOutputForSending();
107 | IMode mode = ModeFactory.CreateMode(mModeClass, bitmap, output);
108 | if (mode != null)
109 | enqueue(mode);
110 | }
111 |
112 | void save(Bitmap bitmap, WaveFileOutputContext context) {
113 | if (mSaveWaveThread != null && mSaveWaveThread.isAlive())
114 | return;
115 | IOutput output = OutputFactory.createOutputForSavingAsWave(context);
116 | IMode mode = ModeFactory.CreateMode(mModeClass, bitmap, output);
117 | if (mode != null)
118 | save(mode, context);
119 | }
120 |
121 | private void save(final IMode mode, final WaveFileOutputContext context) {
122 | mSaveWaveThread = new Thread() {
123 | @Override
124 | public void run() {
125 | mode.init();
126 | mProgressBar2.begin(mode.getProcessCount(),
127 | mMessenger.getString(R.string.progressbar_message_saving_to_file, context.getFileName()));
128 |
129 | while (mode.process()) {
130 | mProgressBar2.step();
131 |
132 | synchronized (this) {
133 | if (mQuit)
134 | break;
135 | }
136 | }
137 | mode.finish(mQuit);
138 | mProgressBar2.end();
139 | if (!mQuit)
140 | mMessenger.carrySaveAsWaveIsDoneMessage(context);
141 | }
142 | };
143 | mSaveWaveThread.start();
144 | }
145 |
146 | void stop() {
147 | synchronized (mThread) {
148 | mStop = true;
149 | int size = mQueue.size();
150 | for (int i = 0; i < size; ++i)
151 | mQueue.remove(0).finish(true);
152 | }
153 | }
154 |
155 | private void enqueue(IMode mode) {
156 | synchronized (mThread) {
157 | mQueue.add(mode);
158 | mThread.notify();
159 | }
160 | }
161 |
162 | void destroy() {
163 | synchronized (mThread) {
164 | mQuit = true;
165 | mThread.notify();
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Settings.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.content.Context;
19 | import android.net.Uri;
20 | import android.util.JsonReader;
21 | import android.util.JsonToken;
22 | import android.util.JsonWriter;
23 |
24 | import java.io.File;
25 | import java.io.FileInputStream;
26 | import java.io.FileOutputStream;
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.InputStreamReader;
30 | import java.io.OutputStream;
31 | import java.io.OutputStreamWriter;
32 |
33 | import om.sstvencoder.Modes.ModeFactory;
34 |
35 | class Settings {
36 | private final static String VERSION = "version";
37 | private final static String IMAGE_URI = "image_uri";
38 | private final static String TEXT_OVERLAY_PATH = "text_overlay_path";
39 | private final static String MODE_CLASS_NAME = "mode_class_name";
40 | private final int mVersion;
41 | private final String mFileName;
42 | private Context mContext;
43 | private String mModeClassName;
44 | private String mImageUri;
45 | private String mTextOverlayPath;
46 |
47 | private Settings() {
48 | mVersion = 1;
49 | mFileName = "settings.json";
50 | mModeClassName = ModeFactory.getDefaultModeClassName();
51 | }
52 |
53 | Settings(Context context) {
54 | this();
55 | mContext = context;
56 | }
57 |
58 | boolean load() {
59 | boolean loaded = false;
60 | JsonReader reader = null;
61 | try {
62 | InputStream in = new FileInputStream(getFile());
63 | reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
64 | read(reader);
65 | loaded = true;
66 | } catch (Exception ignore) {
67 | } finally {
68 | if (reader != null) {
69 | try {
70 | reader.close();
71 | } catch (Exception ignore) {
72 | }
73 | }
74 | }
75 | return loaded;
76 | }
77 |
78 | boolean save() {
79 | boolean saved = false;
80 | JsonWriter writer = null;
81 | try {
82 | OutputStream out = new FileOutputStream(getFile());
83 | writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
84 | writer.setIndent(" ");
85 | write(writer);
86 | saved = true;
87 | } catch (Exception ignore) {
88 | } finally {
89 | if (writer != null) {
90 | try {
91 | writer.close();
92 | } catch (Exception ignore) {
93 | }
94 | }
95 | }
96 | return saved;
97 | }
98 |
99 | void setModeClassName(String modeClassName) {
100 | mModeClassName = modeClassName;
101 | }
102 |
103 | String getModeClassName() {
104 | return mModeClassName;
105 | }
106 |
107 | void setImageUri(Uri uri) {
108 | mImageUri = uri == null ? null : uri.toString();
109 | }
110 |
111 | Uri getImageUri() {
112 | if (mImageUri == null)
113 | return null;
114 | return Uri.parse(mImageUri);
115 | }
116 |
117 | File getTextOverlayFile() {
118 | if (mTextOverlayPath == null)
119 | mTextOverlayPath = new File(mContext.getFilesDir(), "text_overlay.json").getPath();
120 | return new File(mTextOverlayPath);
121 | }
122 |
123 | private File getFile() {
124 | return new File(mContext.getFilesDir(), mFileName);
125 | }
126 |
127 | private void write(JsonWriter writer) throws IOException {
128 | writer.beginObject();
129 | {
130 | writeVersion(writer);
131 | writeModeClassName(writer);
132 | writeImageUri(writer);
133 | writeTextOverlayPath(writer);
134 | }
135 | writer.endObject();
136 | }
137 |
138 | private void writeVersion(JsonWriter writer) throws IOException {
139 | writer.name(VERSION).value(mVersion);
140 | }
141 |
142 | private void writeModeClassName(JsonWriter writer) throws IOException {
143 | writer.name(MODE_CLASS_NAME).value(mModeClassName);
144 | }
145 |
146 | private void writeImageUri(JsonWriter writer) throws IOException {
147 | writer.name(IMAGE_URI).value(mImageUri);
148 | }
149 |
150 | private void writeTextOverlayPath(JsonWriter writer) throws IOException {
151 | writer.name(TEXT_OVERLAY_PATH).value(mTextOverlayPath);
152 | }
153 |
154 | private void read(JsonReader reader) throws IOException {
155 | reader.beginObject();
156 | {
157 | if (readVersion(reader) == mVersion) {
158 | readModeClassName(reader);
159 | readImageUri(reader);
160 | readTextOverlayPath(reader);
161 | }
162 | }
163 | reader.endObject();
164 | }
165 |
166 | private int readVersion(JsonReader reader) throws IOException {
167 | reader.nextName();
168 | return reader.nextInt();
169 | }
170 |
171 | private void readModeClassName(JsonReader reader) throws IOException {
172 | reader.nextName();
173 | mModeClassName = reader.nextString();
174 | }
175 |
176 | private void readImageUri(JsonReader reader) throws IOException {
177 | reader.nextName();
178 | if (reader.peek() == JsonToken.NULL) {
179 | reader.nextNull();
180 | mImageUri = null;
181 | } else
182 | mImageUri = reader.nextString();
183 | }
184 |
185 | private void readTextOverlayPath(JsonReader reader) throws IOException {
186 | reader.nextName();
187 | mTextOverlayPath = reader.nextString();
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlay/LabelCollection.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.TextOverlay;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Rect;
20 | import androidx.annotation.NonNull;
21 |
22 | import java.io.IOException;
23 | import java.util.LinkedList;
24 | import java.util.List;
25 |
26 | public class LabelCollection {
27 | private class Size {
28 | private float mW, mH;
29 |
30 | Size(float w, float h) {
31 | mW = w;
32 | mH = h;
33 | }
34 |
35 | float width() {
36 | return mW;
37 | }
38 |
39 | float height() {
40 | return mH;
41 | }
42 | }
43 |
44 | private final int mVersion;
45 | private final List mLabels;
46 | private Size mScreenSize;
47 | private float mTextSizeFactor;
48 | private LabelContainer mActiveLabel, mEditLabel;
49 | private float mPreviousX, mPreviousY;
50 |
51 | public LabelCollection() {
52 | mVersion = 1;
53 | mLabels = new LinkedList<>();
54 | mPreviousX = 0f;
55 | mPreviousY = 0f;
56 | }
57 |
58 | public void update(float w, float h, float textSizeFactor) {
59 | if (mScreenSize != null) {
60 | float x = (w - mScreenSize.width()) / 2f;
61 | float y = (h - mScreenSize.height()) / 2f;
62 | for (LabelContainer label : mLabels)
63 | label.offset(x, y);
64 | }
65 | mScreenSize = new Size(w, h);
66 | mTextSizeFactor = textSizeFactor;
67 | for (LabelContainer label : mLabels)
68 | label.update(mTextSizeFactor, w, h);
69 | }
70 |
71 | public void draw(Canvas canvas) {
72 | for (LabelContainer label : mLabels)
73 | label.draw(canvas);
74 | if (mActiveLabel != null)
75 | mActiveLabel.drawActive(canvas);
76 | }
77 |
78 | public void draw(Canvas canvas, Rect src, Rect dst) {
79 | for (LabelContainer label : mLabels)
80 | label.draw(canvas, src, dst);
81 | }
82 |
83 | public boolean moveLabelBegin(float x, float y) {
84 | mActiveLabel = find(x, y);
85 | if (mActiveLabel == null)
86 | return false;
87 | mLabels.remove(mActiveLabel);
88 | mPreviousX = x;
89 | mPreviousY = y;
90 | mActiveLabel.jumpInside(mTextSizeFactor, mScreenSize.width(), mScreenSize.height());
91 | return true;
92 | }
93 |
94 | public void moveLabel(float x, float y) {
95 | mActiveLabel.offset(x - mPreviousX, y - mPreviousY);
96 | mActiveLabel.update(mTextSizeFactor, mScreenSize.width(), mScreenSize.height());
97 | mPreviousX = x;
98 | mPreviousY = y;
99 | }
100 |
101 | public void moveLabelEnd() {
102 | mLabels.add(mActiveLabel);
103 | mActiveLabel = null;
104 | mPreviousX = 0f;
105 | mPreviousY = 0f;
106 | }
107 |
108 | public Label editLabelBegin(float x, float y) {
109 | mEditLabel = find(x, y);
110 | if (mEditLabel == null) {
111 | mEditLabel = new LabelContainer(new Label());
112 | mEditLabel.offset(x, y);
113 | }
114 | return mEditLabel.getContent();
115 | }
116 |
117 | public void editLabelEnd(Label label) {
118 | if (mEditLabel != null && label != null) {
119 | if ("".equals(label.getText().trim())) {
120 | if (mLabels.contains(mEditLabel))
121 | mLabels.remove(mEditLabel);
122 | } else {
123 | if (!mLabels.contains(mEditLabel))
124 | mLabels.add(mEditLabel);
125 | mEditLabel.setContent(label);
126 | mEditLabel.update(mTextSizeFactor, mScreenSize.width(), mScreenSize.height());
127 | }
128 | }
129 | mEditLabel = null;
130 | }
131 |
132 | private LabelContainer find(float x, float y) {
133 | for (LabelContainer label : mLabels) {
134 | if (label.contains(x, y))
135 | return label;
136 | }
137 | return null;
138 | }
139 |
140 | private void add(LabelContainer label) {
141 | if (mLabels.size() == 0)
142 | mLabels.add(label);
143 | else
144 | mLabels.add(0, label);
145 | }
146 |
147 | public void write(@NonNull IWriter writer) throws IOException {
148 | writer.beginRootObject();
149 | {
150 | writer.write("version", mVersion);
151 | writer.write("width", mScreenSize.width());
152 | writer.write("height", mScreenSize.height());
153 | writer.write("factor", mTextSizeFactor);
154 | writer.beginArray("labels");
155 | {
156 | for (LabelContainer label : mLabels)
157 | label.write(writer);
158 | }
159 | writer.endArray();
160 | }
161 | writer.endObject();
162 | }
163 |
164 | public boolean read(@NonNull IReader reader) throws IOException {
165 | reader.beginRootObject();
166 | {
167 | if (reader.readInt() != mVersion)
168 | return false;
169 |
170 | float w = reader.readFloat();
171 | float h = reader.readFloat();
172 | float textSizeFactor = reader.readFloat();
173 | reader.beginArray();
174 | {
175 | while (reader.hasNext()) {
176 | LabelContainer label = new LabelContainer(new Label());
177 | label.read(reader);
178 | add(label);
179 | }
180 | }
181 | reader.endArray();
182 | update(w, h, textSizeFactor);
183 | }
184 | reader.endObject();
185 | return true;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/TextOverlayTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import androidx.annotation.NonNull;
19 | import android.util.JsonReader;
20 | import android.util.JsonToken;
21 | import android.util.JsonWriter;
22 |
23 | import java.io.File;
24 | import java.io.FileInputStream;
25 | import java.io.FileOutputStream;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.io.InputStreamReader;
29 | import java.io.OutputStream;
30 | import java.io.OutputStreamWriter;
31 |
32 | import om.sstvencoder.TextOverlay.IReader;
33 | import om.sstvencoder.TextOverlay.IWriter;
34 | import om.sstvencoder.TextOverlay.LabelCollection;
35 |
36 | class TextOverlayTemplate {
37 | private class LabelCollectionWriter implements IWriter {
38 | private JsonWriter mWriter;
39 |
40 | private LabelCollectionWriter(@NonNull JsonWriter writer) {
41 | mWriter = writer;
42 | }
43 |
44 | @Override
45 | public void beginRootObject() throws IOException {
46 | mWriter.beginObject();
47 | }
48 |
49 | @Override
50 | public void beginObject(@NonNull String name) throws IOException {
51 | mWriter.name(name);
52 | mWriter.beginObject();
53 | }
54 |
55 | @Override
56 | public void endObject() throws IOException {
57 | mWriter.endObject();
58 | }
59 |
60 | @Override
61 | public void beginArray(@NonNull String name) throws IOException {
62 | mWriter.name(name);
63 | mWriter.beginArray();
64 | }
65 |
66 | @Override
67 | public void endArray() throws IOException {
68 | mWriter.endArray();
69 | }
70 |
71 | @Override
72 | public void write(@NonNull String name, String value) throws IOException {
73 | mWriter.name(name).value(value);
74 | }
75 |
76 | @Override
77 | public void write(@NonNull String name, boolean value) throws IOException {
78 | mWriter.name(name).value(value);
79 | }
80 |
81 | @Override
82 | public void write(@NonNull String name, float value) throws IOException {
83 | mWriter.name(name).value(value);
84 | }
85 |
86 | @Override
87 | public void write(@NonNull String name, int value) throws IOException {
88 | mWriter.name(name).value(value);
89 | }
90 | }
91 |
92 | private class LabelCollectionReader implements IReader {
93 | private JsonReader mReader;
94 |
95 | private LabelCollectionReader(@NonNull JsonReader reader) {
96 | mReader = reader;
97 | }
98 |
99 | @Override
100 | public void beginRootObject() throws IOException {
101 | mReader.beginObject();
102 | }
103 |
104 | @Override
105 | public void beginObject() throws IOException {
106 | mReader.nextName();
107 | mReader.beginObject();
108 | }
109 |
110 | @Override
111 | public void endObject() throws IOException {
112 | mReader.endObject();
113 | }
114 |
115 | @Override
116 | public void beginArray() throws IOException {
117 | mReader.nextName();
118 | mReader.beginArray();
119 | }
120 |
121 | @Override
122 | public void endArray() throws IOException {
123 | mReader.endArray();
124 | }
125 |
126 | @Override
127 | public boolean hasNext() throws IOException {
128 | return mReader.hasNext();
129 | }
130 |
131 | @Override
132 | public String readString() throws IOException {
133 | mReader.nextName();
134 | if (mReader.peek() == JsonToken.NULL) {
135 | mReader.nextNull();
136 | return null;
137 | }
138 | return mReader.nextString();
139 | }
140 |
141 | @Override
142 | public boolean readBoolean() throws IOException {
143 | mReader.nextName();
144 | return mReader.nextBoolean();
145 | }
146 |
147 | @Override
148 | public float readFloat() throws IOException {
149 | mReader.nextName();
150 | return Float.valueOf(mReader.nextString());
151 | }
152 |
153 | @Override
154 | public int readInt() throws IOException {
155 | mReader.nextName();
156 | return mReader.nextInt();
157 | }
158 | }
159 |
160 | boolean load(@NonNull LabelCollection labels, File file) {
161 | boolean loaded = false;
162 | JsonReader jsonReader = null;
163 | try {
164 | InputStream in = new FileInputStream(file);
165 | jsonReader = new JsonReader(new InputStreamReader(in, "UTF-8"));
166 | loaded = labels.read(new LabelCollectionReader(jsonReader));
167 | } catch (Exception ignore) {
168 | } finally {
169 | if (jsonReader != null) {
170 | try {
171 | jsonReader.close();
172 | } catch (Exception ignore) {
173 | }
174 | }
175 | }
176 | return loaded;
177 | }
178 |
179 | boolean save(@NonNull LabelCollection labels, File file) {
180 | boolean saved = false;
181 | JsonWriter jsonWriter = null;
182 | try {
183 | OutputStream out = new FileOutputStream(file);
184 | jsonWriter = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
185 | jsonWriter.setIndent(" ");
186 | labels.write(new LabelCollectionWriter(jsonWriter));
187 | saved = true;
188 | } catch (Exception ignore) {
189 | } finally {
190 | if (jsonWriter != null) {
191 | try {
192 | jsonWriter.close();
193 | } catch (Exception ignore) {
194 | }
195 | }
196 | }
197 | return saved;
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/ColorPalette/GridColorPalette.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder.ColorPalette;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Color;
20 | import android.graphics.Paint;
21 | import android.graphics.RectF;
22 |
23 | class GridColorPalette implements IColorPalette {
24 |
25 | static int[] getStandardColors() {
26 | return new int[]{
27 | Color.BLACK,
28 | Color.GRAY,
29 | Color.LTGRAY,
30 | Color.WHITE,
31 | Color.YELLOW,
32 | Color.CYAN,
33 | Color.GREEN,
34 | Color.MAGENTA,
35 | Color.RED,
36 | Color.BLUE
37 | };
38 | }
39 |
40 | private final static float STROKE_WIDTH_FACTOR = 6f;
41 | private final static float BOX_SIZE_DP = 96f;
42 | private final static float SPACE_FACTOR = 6f;
43 | private final int[] mColorList;
44 | private final Paint mPaint;
45 | private final RectF mSelectedBounds;
46 | private final float mDisplayMetricsDensity;
47 | private int mColumns, mRows;
48 | private float mWidth, mHeight;
49 | private float mBoxSize, mSpace, mStrokeWidth, mCornerRadius;
50 | private int mSelectedColorIndex;
51 | private boolean mValid;
52 |
53 | GridColorPalette(int[] colorList, float displayMetricsDensity) {
54 | final float CORNER_RADIUS = 3f;
55 | mColorList = colorList;
56 | mDisplayMetricsDensity = displayMetricsDensity;
57 | mCornerRadius = CORNER_RADIUS * mDisplayMetricsDensity;
58 | mPaint = new Paint();
59 | setPaintStyleForBox();
60 | mSelectedBounds = new RectF();
61 | mSelectedColorIndex = 0;
62 | mValid = false;
63 | }
64 |
65 | @Override
66 | public void updateSize(float width, float height) {
67 | mValid = width > 0 && height > 0;
68 |
69 | if (mValid && (mWidth != width || mHeight != height)) {
70 | mWidth = width;
71 | mHeight = height;
72 | updateGrid();
73 | mStrokeWidth = mSpace / STROKE_WIDTH_FACTOR;
74 | setSelectedColor(mSelectedColorIndex);
75 | }
76 | }
77 |
78 | // The approximately same box size independently on resolution has the higher priority.
79 | // Thus the possible filling of the last row is not supported here.
80 | private void updateGrid() {
81 | int boxes = mColorList.length;
82 | mBoxSize = BOX_SIZE_DP * mDisplayMetricsDensity;
83 | mSpace = mBoxSize / SPACE_FACTOR;
84 |
85 | mColumns = min((int) ((mWidth - mSpace) / (mBoxSize + mSpace) + 0.5f), boxes);
86 | mRows = (boxes + mColumns - 1) / mColumns; // ceil
87 | updateBoxSizeAndSpace();
88 |
89 | while (mRows * (mBoxSize + mSpace) + mSpace > mHeight) {
90 | ++mColumns;
91 | mRows = (boxes + mColumns - 1) / mColumns;
92 | updateBoxSizeAndSpace();
93 | }
94 | }
95 |
96 | private int min(int a, int b) {
97 | return a <= b ? a : b;
98 | }
99 |
100 | // Fill out the whole width of the View.
101 | private void updateBoxSizeAndSpace() {
102 | // Set 'space = boxSize / spaceFactor' into
103 | // 'boxSize = (width - (columns + 1) * space ) / columns'
104 | // and solve for boxSize:
105 | mBoxSize = SPACE_FACTOR * mWidth / (1f + mColumns * (SPACE_FACTOR + 1f));
106 | mSpace = mBoxSize / SPACE_FACTOR;
107 | }
108 |
109 | @Override
110 | public void draw(Canvas canvas) {
111 | if (!mValid)
112 | return;
113 |
114 | float x = mSpace, y = mSpace;
115 | float maxX = mColumns * (mBoxSize + mSpace);
116 | for (int color : mColorList) {
117 | RectF rect = new RectF(x, y, x + mBoxSize, y + mBoxSize);
118 | mPaint.setColor(color);
119 | canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, mPaint);
120 | x += mBoxSize + mSpace;
121 | if (x > maxX) {
122 | x = mSpace;
123 | y += mBoxSize + mSpace;
124 | }
125 | }
126 | drawSelectedRect(canvas);
127 | }
128 |
129 | private void drawSelectedRect(Canvas canvas) {
130 | float padding = mSpace / 2f;
131 | float l = mSelectedBounds.left;
132 | float t = mSelectedBounds.top;
133 | float r = mSelectedBounds.right;
134 | float b = mSelectedBounds.bottom;
135 | RectF rect = new RectF(l - padding, t - padding, r + padding, b + padding);
136 | Paint.Style paintStyle = mPaint.getStyle();
137 | setPaintStyleForSelectedBox();
138 | canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, mPaint);
139 | mPaint.setStyle(paintStyle);
140 | }
141 |
142 | private void setPaintStyleForSelectedBox() {
143 | mPaint.setStyle(Paint.Style.STROKE);
144 | mPaint.setStrokeWidth(mStrokeWidth);
145 | mPaint.setColor(Color.WHITE);
146 | }
147 |
148 | private void setPaintStyleForBox() {
149 | mPaint.setStyle(Paint.Style.FILL);
150 | mPaint.setAntiAlias(true);
151 | }
152 |
153 | @Override
154 | public int getSelectedColor() {
155 | return mColorList[mSelectedColorIndex];
156 | }
157 |
158 | @Override
159 | public boolean selectColor(float x, float y) {
160 | if (!mValid || mSelectedBounds.contains(x, y))
161 | return false;
162 |
163 | int column = (int) (x / (mBoxSize + mSpace));
164 | int row = (int) (y / (mBoxSize + mSpace));
165 | if (0 > row || row >= mRows || 0 > column || column >= mColumns)
166 | return false;
167 |
168 | int i = row * mColumns + column;
169 | if (i >= mColorList.length || i == mSelectedColorIndex)
170 | return false;
171 |
172 | float left = mSpace + column * (mBoxSize + mSpace);
173 | float top = mSpace + row * (mBoxSize + mSpace);
174 | if (left > x || x > left + mBoxSize || top > y || y > top + mBoxSize)
175 | return false;
176 |
177 | mSelectedBounds.set(left, top, left + mBoxSize, top + mBoxSize);
178 | mSelectedColorIndex = i;
179 | return true;
180 | }
181 |
182 | @Override
183 | public boolean selectColor(int color) {
184 | for (int i = 0; i < mColorList.length; ++i) {
185 | if (color == mColorList[i]) {
186 | if (mValid)
187 | setSelectedColor(i);
188 | else
189 | mSelectedColorIndex = i;
190 | return true;
191 | }
192 | }
193 | return false;
194 | }
195 |
196 | private void setSelectedColor(int i) {
197 | int row = i / mColumns;
198 | int column = i - row * mColumns;
199 | float x = mSpace + column * (mBoxSize + mSpace);
200 | float y = mSpace + row * (mBoxSize + mSpace);
201 | mSelectedBounds.set(x, y, x + mBoxSize, y + mBoxSize);
202 | mSelectedColorIndex = i;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/FontFamilySet.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import androidx.annotation.NonNull;
19 |
20 | import android.content.Context;
21 | import android.util.Xml;
22 |
23 | import org.xmlpull.v1.XmlPullParser;
24 | import org.xmlpull.v1.XmlPullParserException;
25 |
26 | import java.io.File;
27 | import java.io.FileInputStream;
28 | import java.io.IOException;
29 | import java.io.InputStream;
30 | import java.util.ArrayList;
31 | import java.util.List;
32 |
33 | class FontFamilySet {
34 | class FontFamily {
35 | String name;
36 | String displayName;
37 | boolean bold;
38 | boolean italic;
39 | }
40 |
41 | private final List mFamilySet;
42 | private final Context mContext;
43 |
44 | FontFamilySet(Context context) {
45 | mContext = context;
46 | mFamilySet = new ArrayList<>();
47 | fillWithSystemFonts(mFamilySet);
48 | if (mFamilySet.size() == 0)
49 | mFamilySet.add(getDefaultFontFamily());
50 | }
51 |
52 | @NonNull
53 | private FontFamily getDefaultFontFamily() {
54 | FontFamily defaultFontFamily = new FontFamily();
55 | defaultFontFamily.name = null;
56 | defaultFontFamily.displayName = mContext.getString(R.string.font_default);
57 | defaultFontFamily.bold = true;
58 | defaultFontFamily.italic = true;
59 | return defaultFontFamily;
60 | }
61 |
62 | @NonNull
63 | FontFamily getFontFamily(String name) {
64 | if (name != null) {
65 | for (FontFamily fontFamily : mFamilySet) {
66 | if (name.equals(fontFamily.name))
67 | return fontFamily;
68 | }
69 | }
70 | return mFamilySet.get(0);
71 | }
72 |
73 | @NonNull
74 | FontFamily getFontFamilyFromDisplayName(@NonNull String displayName) {
75 | for (FontFamily fontFamily : mFamilySet) {
76 | if (displayName.equals(fontFamily.displayName))
77 | return fontFamily;
78 | }
79 | return mFamilySet.get(0);
80 | }
81 |
82 | @NonNull
83 | List getFontFamilyDisplayNameList() {
84 | List names = new ArrayList<>();
85 | for (FontFamily fontFamily : mFamilySet)
86 | names.add(fontFamily.displayName);
87 | return names;
88 | }
89 |
90 | private void fillWithSystemFonts(@NonNull List familySet) {
91 | File fontsFile = new File("/system/etc/system_fonts.xml");
92 | if (!fontsFile.exists()) {
93 | fontsFile = new File("/system/etc/fonts.xml");
94 | if (!fontsFile.exists())
95 | return;
96 | }
97 | InputStream in = null;
98 | try {
99 | in = new FileInputStream(fontsFile);
100 | XmlPullParser parser = Xml.newPullParser();
101 | parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
102 | parser.setInput(in, null);
103 | parser.next();
104 | if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("familyset"))
105 | readFamilySet(parser, familySet);
106 | } catch (Exception ignore) {
107 | } finally {
108 | if (in != null) {
109 | try {
110 | in.close();
111 | } catch (Exception ignore) {
112 | }
113 | }
114 | }
115 | }
116 |
117 | private void readFamilySet(@NonNull XmlPullParser parser, @NonNull List familySet)
118 | throws XmlPullParserException, IOException {
119 | while (parser.next() != XmlPullParser.END_TAG) {
120 | if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("family")) {
121 | FontFamily fontFamily = readFamily(parser);
122 | if (fontFamily.displayName != null)
123 | familySet.add(fontFamily);
124 | }
125 | }
126 | }
127 |
128 | @NonNull
129 | private FontFamily readFamily(@NonNull XmlPullParser parser)
130 | throws XmlPullParserException, IOException {
131 | FontFamily fontFamily = new FontFamily();
132 |
133 | while (parser.next() != XmlPullParser.END_TAG) {
134 | if (parser.getEventType() == XmlPullParser.START_TAG) {
135 | switch (parser.getName()) {
136 | case "nameset":
137 | readNameSet(parser, fontFamily);
138 | break;
139 | case "fileset":
140 | readFileSet(parser, fontFamily);
141 | break;
142 | }
143 | }
144 | }
145 | return fontFamily;
146 | }
147 |
148 | private void readNameSet(@NonNull XmlPullParser parser, @NonNull FontFamily fontFamily)
149 | throws XmlPullParserException, IOException {
150 | while (parser.next() != XmlPullParser.END_TAG) {
151 | if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("name")) {
152 | if (fontFamily.name == null)
153 | fontFamily.name = readText(parser);
154 | else {
155 | // skip all other names
156 | parser.next();
157 | parser.next();
158 | }
159 | }
160 | }
161 | }
162 |
163 | private void readFileSet(@NonNull XmlPullParser parser, @NonNull FontFamily fontFamily)
164 | throws XmlPullParserException, IOException {
165 | while (parser.next() != XmlPullParser.END_TAG) {
166 | if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("file"))
167 | parseDisplayNameAndStyle(readText(parser), fontFamily);
168 | }
169 | }
170 |
171 | private void parseDisplayNameAndStyle(String fontFileName, @NonNull FontFamily fontFamily) {
172 | // Example: RobotoCondensed-LightItalic.ttf
173 | // { "RobotoCondensed", "LightItalic" }
174 | String[] familyInfo = fontFileName.split("\\.")[0].split("-");
175 | String s = "";
176 | if (familyInfo.length > 1) {
177 | s = familyInfo[1];
178 | if (s.contains("Bold"))
179 | fontFamily.bold = true;
180 | if (s.contains("Italic"))
181 | fontFamily.italic = true;
182 | }
183 | if (fontFamily.displayName == null) {
184 | // "Light"
185 | s = s.replace("Regular", "").replace("Bold", "").replace("Italic", "");
186 | // "Roboto Condensed Light"
187 | fontFamily.displayName = (familyInfo[0] + s).replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2");
188 | }
189 | }
190 |
191 | private String readText(@NonNull XmlPullParser parser)
192 | throws IOException, XmlPullParserException {
193 | String text = "";
194 | if (parser.next() == XmlPullParser.TEXT) {
195 | text = parser.getText();
196 | parser.next();
197 | }
198 | return text;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/Utility.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.content.Context;
19 | import android.content.Intent;
20 | import android.graphics.Rect;
21 |
22 | import androidx.exifinterface.media.ExifInterface;
23 |
24 | import android.graphics.Typeface;
25 | import android.net.Uri;
26 | import android.os.Build;
27 | import android.os.Environment;
28 |
29 | import androidx.annotation.NonNull;
30 | import androidx.core.content.FileProvider;
31 |
32 | import java.io.File;
33 | import java.text.SimpleDateFormat;
34 | import java.util.ArrayList;
35 | import java.util.Date;
36 | import java.util.List;
37 | import java.util.Locale;
38 |
39 | public final class Utility {
40 | private static final String DIRECTORY_SYSTEM_FONTS = "/system/fonts";
41 | private static final String DEFAULT_FONT_FAMILY = "Default";
42 |
43 | @NonNull
44 | static Rect getEmbeddedRect(int w, int h, int iw, int ih) {
45 | Rect rect;
46 |
47 | int ow = (9 * w) / 10;
48 | int oh = (9 * h) / 10;
49 |
50 | if (iw * oh < ow * ih) {
51 | int right = (iw * oh) / ih;
52 | rect = new Rect(0, 0, right, oh);
53 | rect.offset((w - right) / 2, (h - oh) / 2);
54 | } else {
55 | int bottom = (ih * ow) / iw;
56 | rect = new Rect(0, 0, ow, bottom);
57 | rect.offset((w - ow) / 2, (h - bottom) / 2);
58 | }
59 | return rect;
60 | }
61 |
62 | static float getTextSizeFactor(int w, int h) {
63 | return 0.1f * (Utility.getEmbeddedRect(w, h, 320, 240).height());
64 | }
65 |
66 | static String createMessage(Exception ex) {
67 | StringBuilder sb = new StringBuilder();
68 | sb.append(ex.getMessage());
69 | sb.append("\n");
70 | for (StackTraceElement el : ex.getStackTrace()) {
71 | sb.append("\n");
72 | sb.append(el.toString());
73 | }
74 | return sb.toString();
75 | }
76 |
77 | @NonNull
78 | static Intent createEmailIntent(final String subject, final String text) {
79 | Intent intent = new Intent(Intent.ACTION_SEND);
80 | intent.setType("text/email");
81 | intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"olga.rgb@gmail.com"});
82 | intent.putExtra(Intent.EXTRA_SUBJECT, subject);
83 | intent.putExtra(Intent.EXTRA_TEXT, text);
84 | return intent;
85 | }
86 |
87 | static int convertToDegrees(int exifOrientation) {
88 | switch (exifOrientation) {
89 | case ExifInterface.ORIENTATION_ROTATE_90:
90 | return 90;
91 | case ExifInterface.ORIENTATION_ROTATE_180:
92 | return 180;
93 | case ExifInterface.ORIENTATION_ROTATE_270:
94 | return 270;
95 | }
96 | return 0;
97 | }
98 |
99 | static Uri createImageUri(Context context) {
100 | if (!isExternalStorageWritable())
101 | return null;
102 | File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
103 | File file = new File(dir, createFileName() + ".jpg");
104 |
105 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
106 | // API level 24 and higher: FileUriExposedException
107 | return Uri.fromFile(file); // file:// URI
108 | // API level 15: Camera crash
109 | return FileProvider.getUriForFile(context, "om.sstvencoder", file); // content:// URI
110 | }
111 |
112 | static String createWaveFileName() {
113 | return createFileName() + ".wav";
114 | }
115 |
116 | private static String createFileName() {
117 | return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
118 | }
119 |
120 | static boolean isExternalStorageWritable() {
121 | String state = Environment.getExternalStorageState();
122 | return Environment.MEDIA_MOUNTED.equals(state);
123 | }
124 |
125 | static List getSystemFontFamilyList() {
126 | List fontFamilyNameList = new ArrayList<>();
127 | File fontsDir = new File(DIRECTORY_SYSTEM_FONTS);
128 |
129 | if (fontsDir.exists() && fontsDir.isDirectory()) {
130 | File[] files = fontsDir.listFiles();
131 | if (files != null) {
132 | for (File file : files) {
133 | String fileName = file.getName();
134 | if (file.isFile() && isSupportedFontFileFormat(fileName)) {
135 | String fontFamilyName = getFontFamilyName(fileName);
136 | if (!fontFamilyNameList.contains(fontFamilyName))
137 | fontFamilyNameList.add(fontFamilyName);
138 | }
139 | }
140 | }
141 | }
142 |
143 | fontFamilyNameList.add(0, Utility.DEFAULT_FONT_FAMILY);
144 | return fontFamilyNameList;
145 | }
146 |
147 | private static boolean isSupportedFontFileFormat(String fileName) {
148 | return fileName.endsWith(".ttf") || fileName.endsWith(".otf");
149 | }
150 |
151 | private static String getFontFamilyName(String fileName) {
152 | String fontFamilyName = fileName;
153 | int lastIndex = fileName.length() - 1;
154 |
155 | int charIndex = fileName.indexOf('-');
156 | if (0 < charIndex && charIndex < lastIndex) {
157 | fontFamilyName = fileName.substring(0, charIndex);
158 | } else {
159 | charIndex = fileName.lastIndexOf('.');
160 | if (0 < charIndex && charIndex < lastIndex) {
161 | fontFamilyName = fileName.substring(0, charIndex);
162 | }
163 | }
164 | return fontFamilyName;
165 | }
166 |
167 | public static String getFontFilePath(String fontFamilyName, int style) {
168 | List fontFamilyFilePathList = getFontFamilyFilePathList(fontFamilyName);
169 | String fontFilePath = fontFamilyFilePathList.get(0);
170 |
171 | String styleString = getFontFileStyleString(style);
172 | if (!styleString.isEmpty()) {
173 | for (String path : fontFamilyFilePathList) {
174 | if (path.contains(styleString)) {
175 | fontFilePath = path;
176 | break;
177 | }
178 | }
179 | }
180 | return fontFilePath;
181 | }
182 |
183 | private static List getFontFamilyFilePathList(String fontFamilyName) {
184 | List fontFamilyFilePathList = new ArrayList<>();
185 | File fontsDir = new File(DIRECTORY_SYSTEM_FONTS);
186 |
187 | if (fontsDir.exists() && fontsDir.isDirectory()) {
188 | File[] files = fontsDir.listFiles();
189 | if (files != null) {
190 | for (File file : files) {
191 | if (file.isFile()) {
192 | String path = file.getAbsolutePath();
193 | if (path.contains(fontFamilyName)) {
194 | fontFamilyFilePathList.add(path);
195 | }
196 | }
197 | }
198 | }
199 | }
200 | return fontFamilyFilePathList;
201 | }
202 |
203 | private static String getFontFileStyleString(int style) {
204 | if (style == Typeface.NORMAL)
205 | return "-Regular";
206 | if (style == Typeface.BOLD_ITALIC)
207 | return "-BoldItalic";
208 | if (style == Typeface.BOLD)
209 | return "-Bold";
210 | if (style == Typeface.ITALIC)
211 | return "-Italic";
212 | return "";
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/app/src/main/java/om/sstvencoder/EditTextActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Olga Miller
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package om.sstvencoder;
17 |
18 | import android.content.Intent;
19 | import android.graphics.Color;
20 | import android.graphics.drawable.Drawable;
21 |
22 | import androidx.annotation.ColorInt;
23 | import androidx.fragment.app.DialogFragment;
24 | import androidx.core.content.ContextCompat;
25 | import androidx.appcompat.app.AppCompatActivity;
26 |
27 | import android.os.Bundle;
28 | import android.text.Editable;
29 | import android.text.TextWatcher;
30 | import android.view.Menu;
31 | import android.view.MenuItem;
32 | import android.view.MotionEvent;
33 | import android.view.View;
34 | import android.widget.AdapterView;
35 | import android.widget.ArrayAdapter;
36 | import android.widget.CheckBox;
37 | import android.widget.EditText;
38 | import android.widget.Spinner;
39 |
40 | import java.util.List;
41 |
42 | import om.sstvencoder.TextOverlay.Label;
43 |
44 | public class EditTextActivity extends AppCompatActivity
45 | implements AdapterView.OnItemSelectedListener, ColorFragment.OnColorSelectedListener {
46 |
47 | private enum EditColorMode {
48 | None,
49 | Text,
50 | Outline
51 | }
52 |
53 | public static final int REQUEST_CODE = 101;
54 | public static final String EXTRA = "EDIT_TEXT_EXTRA";
55 | private Label mLabel;
56 | private EditColorMode mEditColor;
57 | private List mFontFamilyNameList;
58 | private CheckBox mEditItalic, mEditBold, mEditOutline;
59 | private int mClearTextButtonWidth;
60 |
61 | @Override
62 | protected void onCreate(Bundle savedInstanceState) {
63 | super.onCreate(savedInstanceState);
64 | setContentView(R.layout.activity_edit_text);
65 | mEditColor = EditColorMode.None;
66 | mEditBold = findViewById(R.id.edit_bold);
67 | mEditItalic = findViewById(R.id.edit_italic);
68 | mEditOutline = findViewById(R.id.edit_outline);
69 | }
70 |
71 | @Override
72 | protected void onStart() {
73 | super.onStart();
74 | mLabel = ((Label) getIntent().getSerializableExtra(EXTRA)).getClone();
75 | initText();
76 | initTextSizeSpinner(mLabel.getTextSize());
77 | mEditBold.setChecked(mLabel.getBold());
78 | mEditItalic.setChecked(mLabel.getItalic());
79 | initFontFamilySpinner(mLabel.getFamilyName());
80 | mEditOutline.setChecked(mLabel.getOutline());
81 | initOutlineSizeSpinner(mLabel.getOutlineSize());
82 | findViewById(R.id.edit_color).setBackgroundColor(mLabel.getForeColor());
83 | findViewById(R.id.edit_outline_color).setBackgroundColor(mLabel.getOutlineColor());
84 | enableOutline(mEditOutline.isChecked());
85 | }
86 |
87 | private void initText() {
88 | EditText editText = findViewById(R.id.edit_text);
89 | int clearTextIcon = android.R.drawable.ic_menu_close_clear_cancel;
90 | Drawable drawable = ContextCompat.getDrawable(this, clearTextIcon);
91 | editText.setText(mLabel.getText());
92 | editText.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null);
93 | mClearTextButtonWidth = 2 * drawable.getIntrinsicWidth();
94 |
95 | editText.addTextChangedListener(new TextWatcher() {
96 | @Override
97 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
98 | }
99 |
100 | @Override
101 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
102 | mLabel.setText(charSequence.toString());
103 | }
104 |
105 | @Override
106 | public void afterTextChanged(Editable editable) {
107 | }
108 | });
109 |
110 | editText.setOnTouchListener(new View.OnTouchListener() {
111 | private boolean mClear;
112 |
113 | @Override
114 | public boolean onTouch(View view, MotionEvent e) {
115 | switch (e.getAction()) {
116 | case MotionEvent.ACTION_DOWN:
117 | if (HitClearTextButton(view, e)) {
118 | mClear = true;
119 | return true;
120 | }
121 | break;
122 | case MotionEvent.ACTION_MOVE:
123 | if (!HitClearTextButton(view, e))
124 | mClear = false;
125 | break;
126 | case MotionEvent.ACTION_UP:
127 | if (HitClearTextButton(view, e) && mClear) {
128 | ((EditText) view).setText("");
129 | return true;
130 | }
131 | mClear = false;
132 | break;
133 | case MotionEvent.ACTION_CANCEL:
134 | mClear = false;
135 | break;
136 | }
137 | return false;
138 | }
139 |
140 | private boolean HitClearTextButton(View view, MotionEvent e) {
141 | int left = view.getRight() - mClearTextButtonWidth;
142 | return left < e.getX();
143 | }
144 | });
145 | }
146 |
147 | private void initFontFamilySpinner(String familyName) {
148 | Spinner spinner = findViewById(R.id.edit_font_family);
149 | spinner.setOnItemSelectedListener(this);
150 | mFontFamilyNameList = Utility.getSystemFontFamilyList();
151 | spinner.setAdapter(new ArrayAdapter<>(this,
152 | android.R.layout.simple_spinner_dropdown_item, mFontFamilyNameList));
153 | spinner.setSelection(mFontFamilyNameList.indexOf(familyName));
154 | }
155 |
156 | private void initTextSizeSpinner(float textSize) {
157 | Spinner spinner = findViewById(R.id.edit_text_size);
158 | spinner.setOnItemSelectedListener(this);
159 | String[] sizeList = new String[]
160 | {
161 | getString(R.string.font_size_small),
162 | getString(R.string.font_size_normal),
163 | getString(R.string.font_size_large),
164 | getString(R.string.font_size_huge)
165 | };
166 | spinner.setAdapter(new ArrayAdapter<>(this,
167 | android.R.layout.simple_spinner_dropdown_item, sizeList));
168 | spinner.setSelection(textSizeToPosition(textSize));
169 | }
170 |
171 | private void initOutlineSizeSpinner(float outlineSize) {
172 | Spinner spinner = findViewById(R.id.edit_outline_size);
173 | spinner.setOnItemSelectedListener(this);
174 | String[] sizeList = new String[]
175 | {
176 | getString(R.string.outline_size_thin),
177 | getString(R.string.outline_size_normal),
178 | getString(R.string.outline_size_thick)
179 | };
180 | spinner.setAdapter(new ArrayAdapter<>(this,
181 | android.R.layout.simple_spinner_dropdown_item, sizeList));
182 | spinner.setSelection(outlineSizeToPosition(outlineSize));
183 | }
184 |
185 | private int textSizeToPosition(float textSize) {
186 | int position = (int) (textSize - 1f);
187 | if (0 <= position && position <= 3)
188 | return position;
189 | mLabel.setTextSize(Label.TEXT_SIZE_NORMAL);
190 | return 1;
191 | }
192 |
193 | private float positionToTextSize(int position) {
194 | return position + 1f;
195 | }
196 |
197 | private int outlineSizeToPosition(float outlineSize) {
198 | int position = (int) (outlineSize * 2f / Label.OUTLINE_SIZE_NORMAL - 1f);
199 | if (0 <= position && position <= 2)
200 | return position;
201 | mLabel.setOutlineSize(Label.OUTLINE_SIZE_NORMAL);
202 | return 1;
203 | }
204 |
205 | private float positionToOutlineSize(int position) {
206 | return Label.OUTLINE_SIZE_NORMAL * 0.5f * (position + 1f);
207 | }
208 |
209 | @Override
210 | public void onItemSelected(AdapterView> parent, View view, int position, long id) {
211 | int parentId = parent.getId();
212 | if (parentId == R.id.edit_text_size) {
213 | mLabel.setTextSize(positionToTextSize(position));
214 | }
215 | else if (parentId == R.id.edit_outline_size) {
216 | mLabel.setOutlineSize(positionToOutlineSize(position));
217 | }
218 | else if (parentId == R.id.edit_font_family) {
219 | mLabel.setFamilyName(mFontFamilyNameList.get(position));
220 | }
221 | }
222 |
223 | private void enableOutline(boolean enabled) {
224 | findViewById(R.id.text_outline_size).setEnabled(enabled);
225 | findViewById(R.id.edit_outline_size).setEnabled(enabled);
226 | findViewById(R.id.text_outline_color).setEnabled(enabled);
227 | findViewById(R.id.edit_outline_color).setEnabled(enabled);
228 | @ColorInt
229 | int color = enabled ? mLabel.getOutlineColor() : Color.DKGRAY;
230 | findViewById(R.id.edit_outline_color).setBackgroundColor(color);
231 | }
232 |
233 | @Override
234 | public void onNothingSelected(AdapterView> parent) {
235 | }
236 |
237 | @Override
238 | public boolean onCreateOptionsMenu(Menu menu) {
239 | getMenuInflater().inflate(R.menu.menu_edit_text, menu);
240 | return true;
241 | }
242 |
243 | @Override
244 | public boolean onOptionsItemSelected(MenuItem item) {
245 | int id = item.getItemId();
246 | if (id == R.id.action_done) {
247 | done();
248 | return true;
249 | }
250 | return super.onOptionsItemSelected(item);
251 | }
252 |
253 | public void onBoldClick(View view) {
254 | mLabel.setBold(mEditBold.isChecked());
255 | }
256 |
257 | public void onItalicClick(View view) {
258 | mLabel.setItalic(mEditItalic.isChecked());
259 | }
260 |
261 | public void onOutlineClick(View view) {
262 | if (view.getId() == R.id.text_outline)
263 | mEditOutline.setChecked(!mEditOutline.isChecked());
264 | boolean outline = mEditOutline.isChecked();
265 | mLabel.setOutline(outline);
266 | enableOutline(outline);
267 | }
268 |
269 | public void onColorClick(View view) {
270 | showColorDialog(R.string.color, mLabel.getForeColor());
271 | mEditColor = EditColorMode.Text;
272 | }
273 |
274 | public void onOutlineColorClick(View view) {
275 | if (mEditOutline.isChecked()) {
276 | showColorDialog(R.string.outline_color, mLabel.getOutlineColor());
277 | mEditColor = EditColorMode.Outline;
278 | }
279 | }
280 |
281 | private void showColorDialog(int title, int color) {
282 | ColorFragment fragment = new ColorFragment();
283 | fragment.setTitle(title);
284 | fragment.setColor(color);
285 | fragment.addOnColorSelectedListener(this);
286 | fragment.show(getSupportFragmentManager(), ColorFragment.class.getName());
287 | }
288 |
289 | @Override
290 | public void onColorSelected(DialogFragment fragment, int color) {
291 | switch (mEditColor) {
292 | case Text:
293 | mLabel.setForeColor(color);
294 | findViewById(R.id.edit_color).setBackgroundColor(color);
295 | break;
296 | case Outline:
297 | mLabel.setOutlineColor(color);
298 | findViewById(R.id.edit_outline_color).setBackgroundColor(color);
299 | break;
300 | }
301 | mEditColor = EditColorMode.None;
302 | }
303 |
304 | @Override
305 | public void onCancel(DialogFragment fragment) {
306 | mEditColor = EditColorMode.None;
307 | }
308 |
309 | private void done() {
310 | Intent intent = new Intent();
311 | intent.putExtra(EXTRA, mLabel);
312 | setResult(RESULT_OK, intent);
313 | finish();
314 | }
315 | }
316 |
--------------------------------------------------------------------------------