Initial commit
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
202
LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
8
NOTICE
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
SSTV Encoder 2
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Mode specifications were taken from "Dayton Paper" of JL Barber:
|
||||
http://www.barberdsp.com/files/Dayton%20Paper.pdf
|
||||
|
||||
Icons were created using GIMP:
|
||||
http://www.gimp.org/
|
||||
42
README
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
SSTV Encoder 2
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
-------------- Description:
|
||||
|
||||
This app sends images via Slow Scan Television (SSTV).
|
||||
|
||||
Currently supported modes are:
|
||||
Martin Modes: Martin 1, Martin 2
|
||||
PD Modes: PD 50, PD 90, PD 120, PD 160, PD 180, PD 240, PD 290
|
||||
Scottie Modes: Scottie 1, Scottie 2, Scottie DX
|
||||
Robot Modes: Robot 36 Color, Robot 72 Color
|
||||
Wraase Modes: Wraase SC2 180
|
||||
|
||||
-------------- Usage:
|
||||
|
||||
Tap "Take Picture" or "Pick Picture" menu button or
|
||||
use the Share option of any app like Gallery to load an image.
|
||||
|
||||
Single tap to add text.
|
||||
Single tap on text to edit.
|
||||
Long press to move text.
|
||||
|
||||
-------------- Remarks:
|
||||
|
||||
After clicking on a mode the image will be scaled to that mode's native size.
|
||||
To keep the aspect ratio, black borders will be added if necessary.
|
||||
Original image can be resend using another mode without reloading.
|
||||
|
||||
The mode specifications are taken from the Dayton Paper of JL Barber:
|
||||
http://www.barberdsp.com/files/Dayton%20Paper.pdf
|
||||
|
||||
Source code for decoding SSTV images can be found here:
|
||||
https://github.com/xdsopl/robot36/tree/android
|
||||
|
||||
On Google Play you can find the working app:
|
||||
SSTV Encoder
|
||||
https://play.google.com/store/apps/details?id=om.sstvencoder
|
||||
|
||||
For decoding try:
|
||||
Robot36 - SSTV Image Decoder
|
||||
https://play.google.com/store/apps/details?id=xdsopl.robot36
|
||||
1
app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
24
app/build.gradle
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25"
|
||||
defaultConfig {
|
||||
applicationId "om.sstvencoder"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
versionCode 21
|
||||
versionName "2.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:25.0.0'
|
||||
}
|
||||
17
app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/olga/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
45
app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="om.sstvencoder">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"/>
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="om.sstvencoder"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/paths"/>
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="image/*"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".EditTextActivity"/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ColorPalette;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ColorPaletteView extends View {
|
||||
|
||||
public interface OnChangeListener {
|
||||
void onChange(View v, int color);
|
||||
}
|
||||
|
||||
private final ArrayList<OnChangeListener> mListeners;
|
||||
private final IColorPalette mPalette;
|
||||
|
||||
public ColorPaletteView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mListeners = new ArrayList<>();
|
||||
mPalette = new GridColorPalette(GridColorPalette.getStandardColors(),
|
||||
getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mPalette.getSelectedColor();
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mPalette.selectColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
|
||||
super.onSizeChanged(w, h, old_w, old_h);
|
||||
mPalette.updateSize(w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
mPalette.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent e) {
|
||||
boolean consumed = false;
|
||||
switch (e.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
update(e.getX(), e.getY());
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed || super.onTouchEvent(e);
|
||||
}
|
||||
|
||||
private void update(float x, float y) {
|
||||
if (mPalette.selectColor(x, y)) {
|
||||
invalidate();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnChangeListener(OnChangeListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
private void callback() {
|
||||
for (OnChangeListener listener : mListeners) {
|
||||
listener.onChange(this, mPalette.getSelectedColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ColorPalette;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
|
||||
class GridColorPalette implements IColorPalette {
|
||||
|
||||
static int[] getStandardColors() {
|
||||
return new int[]{
|
||||
Color.BLACK,
|
||||
Color.GRAY,
|
||||
Color.LTGRAY,
|
||||
Color.WHITE,
|
||||
Color.YELLOW,
|
||||
Color.CYAN,
|
||||
Color.GREEN,
|
||||
Color.MAGENTA,
|
||||
Color.RED,
|
||||
Color.BLUE
|
||||
};
|
||||
}
|
||||
|
||||
private final static float STROKE_WIDTH_FACTOR = 6f;
|
||||
private final static float BOX_SIZE_DP = 96f;
|
||||
private final static float SPACE_FACTOR = 6f;
|
||||
private final static float CORNER_RADIUS = 6f;
|
||||
private final int[] mColorList;
|
||||
private final Paint mPaint;
|
||||
private final RectF mSelectedBounds;
|
||||
private final float mDisplayMetricsDensity;
|
||||
private int mColumns, mRows;
|
||||
private float mWidth, mHeight;
|
||||
private float mBoxSize, mSpace, mStrokeWidth;
|
||||
private int mSelectedColorIndex;
|
||||
private boolean mValid;
|
||||
|
||||
GridColorPalette(int[] colorList, float displayMetricsDensity) {
|
||||
mColorList = colorList;
|
||||
mDisplayMetricsDensity = displayMetricsDensity;
|
||||
mPaint = new Paint();
|
||||
setPaintStyleForBox();
|
||||
mSelectedBounds = new RectF();
|
||||
mSelectedColorIndex = 0;
|
||||
mValid = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSize(float width, float height) {
|
||||
mValid = width > 0 && height > 0;
|
||||
|
||||
if (mValid && (mWidth != width || mHeight != height)) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
updateGrid();
|
||||
mStrokeWidth = mSpace / STROKE_WIDTH_FACTOR;
|
||||
setSelectedColor(mSelectedColorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// The approximately same box size independently on resolution has the higher priority.
|
||||
// Thus the possible filling of the last row is not supported here.
|
||||
private void updateGrid() {
|
||||
int boxes = mColorList.length;
|
||||
mBoxSize = BOX_SIZE_DP * mDisplayMetricsDensity;
|
||||
mSpace = mBoxSize / SPACE_FACTOR;
|
||||
|
||||
mColumns = min((int) ((mWidth - mSpace) / (mBoxSize + mSpace) + 0.5f), boxes);
|
||||
mRows = (boxes + mColumns - 1) / mColumns; // ceil
|
||||
updateBoxSizeAndSpace();
|
||||
|
||||
while (mRows * (mBoxSize + mSpace) + mSpace > mHeight) {
|
||||
++mColumns;
|
||||
mRows = (boxes + mColumns - 1) / mColumns;
|
||||
updateBoxSizeAndSpace();
|
||||
}
|
||||
}
|
||||
|
||||
private int min(int a, int b) {
|
||||
return a <= b ? a : b;
|
||||
}
|
||||
|
||||
// Fill out the whole width of the View.
|
||||
private void updateBoxSizeAndSpace() {
|
||||
// Set 'space = boxSize / spaceFactor' into
|
||||
// 'boxSize = (width - (columns + 1) * space ) / columns'
|
||||
// and solve for boxSize:
|
||||
mBoxSize = SPACE_FACTOR * mWidth / (1f + mColumns * (SPACE_FACTOR + 1f));
|
||||
mSpace = mBoxSize / SPACE_FACTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (!mValid)
|
||||
return;
|
||||
|
||||
float x = mSpace, y = mSpace;
|
||||
float maxX = mColumns * (mBoxSize + mSpace);
|
||||
for (int color : mColorList) {
|
||||
RectF rect = new RectF(x, y, x + mBoxSize, y + mBoxSize);
|
||||
mPaint.setColor(color);
|
||||
canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, mPaint);
|
||||
x += mBoxSize + mSpace;
|
||||
if (x > maxX) {
|
||||
x = mSpace;
|
||||
y += mBoxSize + mSpace;
|
||||
}
|
||||
}
|
||||
drawSelectedRect(canvas);
|
||||
}
|
||||
|
||||
private void drawSelectedRect(Canvas canvas) {
|
||||
float padding = mSpace / 2f;
|
||||
float l = mSelectedBounds.left;
|
||||
float t = mSelectedBounds.top;
|
||||
float r = mSelectedBounds.right;
|
||||
float b = mSelectedBounds.bottom;
|
||||
RectF rect = new RectF(l - padding, t - padding, r + padding, b + padding);
|
||||
Paint.Style paintStyle = mPaint.getStyle();
|
||||
setPaintStyleForSelectedBox();
|
||||
canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, mPaint);
|
||||
mPaint.setStyle(paintStyle);
|
||||
}
|
||||
|
||||
private void setPaintStyleForSelectedBox() {
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeWidth(mStrokeWidth);
|
||||
mPaint.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
private void setPaintStyleForBox() {
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelectedColor() {
|
||||
return mColorList[mSelectedColorIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selectColor(float x, float y) {
|
||||
if (!mValid || mSelectedBounds.contains(x, y))
|
||||
return false;
|
||||
|
||||
int column = (int) (x / (mBoxSize + mSpace));
|
||||
int row = (int) (y / (mBoxSize + mSpace));
|
||||
if (0 > row || row >= mRows || 0 > column && column >= mColumns)
|
||||
return false;
|
||||
|
||||
int i = row * mColumns + column;
|
||||
if (i >= mColorList.length || i == mSelectedColorIndex)
|
||||
return false;
|
||||
|
||||
float left = mSpace + column * (mBoxSize + mSpace);
|
||||
float top = mSpace + row * (mBoxSize + mSpace);
|
||||
if (left > x || x > left + mBoxSize || top > y || y > top + mBoxSize)
|
||||
return false;
|
||||
|
||||
mSelectedBounds.set(left, top, left + mBoxSize, top + mBoxSize);
|
||||
mSelectedColorIndex = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selectColor(int color) {
|
||||
for (int i = 0; i < mColorList.length; ++i) {
|
||||
if (color == mColorList[i]) {
|
||||
if (mValid)
|
||||
setSelectedColor(i);
|
||||
else
|
||||
mSelectedColorIndex = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setSelectedColor(int i) {
|
||||
int row = i / mColumns;
|
||||
int column = i - row * mColumns;
|
||||
float x = mSpace + column * (mBoxSize + mSpace);
|
||||
float y = mSpace + row * (mBoxSize + mSpace);
|
||||
mSelectedBounds.set(x, y, x + mBoxSize, y + mBoxSize);
|
||||
mSelectedColorIndex = i;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ColorPalette;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
|
||||
interface IColorPalette {
|
||||
void updateSize(float width, float height);
|
||||
|
||||
void draw(Canvas canvas);
|
||||
|
||||
int getSelectedColor();
|
||||
|
||||
boolean selectColor(float x, float y);
|
||||
|
||||
boolean selectColor(int color);
|
||||
}
|
||||
422
app/src/main/java/om/sstvencoder/CropView.java
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.GestureDetectorCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.TextOverlay.Label;
|
||||
import om.sstvencoder.TextOverlay.LabelCollection;
|
||||
|
||||
public class CropView extends ImageView {
|
||||
private class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (!mLongPress) {
|
||||
moveImage(distanceX, distanceY);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mLongPress = false;
|
||||
if (!mInScale && mLabelCollection.moveLabelBegin(e.getX(), e.getY())) {
|
||||
invalidate();
|
||||
mLongPress = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
if (!mLongPress) {
|
||||
editLabelBegin(e.getX(), e.getY());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
if (!mLongPress) {
|
||||
mInScale = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
scaleImage(detector.getScaleFactor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
mInScale = false;
|
||||
}
|
||||
}
|
||||
|
||||
private GestureDetectorCompat mDetectorCompat;
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
private boolean mLongPress, mInScale;
|
||||
private ModeSize mModeSize;
|
||||
private final Paint mPaint, mRectPaint, mBorderPaint;
|
||||
private RectF mInputRect;
|
||||
private Rect mOutputRect;
|
||||
private BitmapRegionDecoder mRegionDecoder;
|
||||
private int mImageWidth, mImageHeight;
|
||||
private Bitmap mCacheBitmap;
|
||||
private boolean mSmallImage;
|
||||
private boolean mImageOK;
|
||||
private final Rect mCanvasDrawRect, mImageDrawRect;
|
||||
private int mOrientation;
|
||||
private Rect mCacheRect;
|
||||
private int mCacheSampleSize;
|
||||
private final BitmapFactory.Options mBitmapOptions;
|
||||
private LabelCollection mLabelCollection;
|
||||
|
||||
public CropView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mDetectorCompat = new GestureDetectorCompat(getContext(), new GestureListener());
|
||||
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener());
|
||||
|
||||
mBitmapOptions = new BitmapFactory.Options();
|
||||
|
||||
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
mRectPaint = new Paint();
|
||||
mRectPaint.setStyle(Paint.Style.STROKE);
|
||||
mRectPaint.setStrokeWidth(2);
|
||||
mBorderPaint = new Paint();
|
||||
mBorderPaint.setColor(Color.BLACK);
|
||||
|
||||
mCanvasDrawRect = new Rect();
|
||||
mImageDrawRect = new Rect();
|
||||
mCacheRect = new Rect();
|
||||
mOutputRect = new Rect();
|
||||
|
||||
mSmallImage = false;
|
||||
mImageOK = false;
|
||||
|
||||
mLabelCollection = new LabelCollection();
|
||||
}
|
||||
|
||||
public void setModeSize(ModeSize size) {
|
||||
mModeSize = size;
|
||||
mOutputRect = Utility.getEmbeddedRect(getWidth(), getHeight(), mModeSize.width(), mModeSize.height());
|
||||
if (mImageOK) {
|
||||
resetInputRect();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetInputRect() {
|
||||
float iw = mModeSize.width();
|
||||
float ih = mModeSize.height();
|
||||
float ow = mImageWidth;
|
||||
float oh = mImageHeight;
|
||||
if (iw * oh > ow * ih) {
|
||||
mInputRect = new RectF(0.0f, 0.0f, (iw * oh) / ih, oh);
|
||||
mInputRect.offset((ow - (iw * oh) / ih) / 2.0f, 0.0f);
|
||||
} else {
|
||||
mInputRect = new RectF(0.0f, 0.0f, ow, (ih * ow) / iw);
|
||||
mInputRect.offset(0.0f, (oh - (ih * ow) / iw) / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void rotateImage(int orientation) {
|
||||
mOrientation += orientation;
|
||||
mOrientation %= 360;
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int tmp = mImageWidth;
|
||||
mImageWidth = mImageHeight;
|
||||
mImageHeight = tmp;
|
||||
}
|
||||
if (mImageOK) {
|
||||
resetInputRect();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setNoBitmap() {
|
||||
mImageOK = false;
|
||||
mOrientation = 0;
|
||||
recycle();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBitmapStream(InputStream stream) throws IOException {
|
||||
mImageOK = false;
|
||||
mOrientation = 0;
|
||||
recycle();
|
||||
|
||||
// app6 + exif
|
||||
int bufferBytes = 1048576;
|
||||
if (!stream.markSupported())
|
||||
stream = new BufferedInputStream(stream, bufferBytes);
|
||||
stream.mark(bufferBytes);
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(new BufferedInputStream(stream), null, options);
|
||||
stream.reset();
|
||||
mImageWidth = options.outWidth;
|
||||
mImageHeight = options.outHeight;
|
||||
|
||||
if (mImageWidth * mImageHeight < 1024 * 1024) {
|
||||
mCacheBitmap = BitmapFactory.decodeStream(stream);
|
||||
mSmallImage = true;
|
||||
} else {
|
||||
mRegionDecoder = BitmapRegionDecoder.newInstance(stream, true);
|
||||
mCacheRect.setEmpty();
|
||||
mSmallImage = false;
|
||||
}
|
||||
|
||||
if (mCacheBitmap == null && mRegionDecoder == null) {
|
||||
String size = options.outWidth + "x" + options.outHeight;
|
||||
throw new IOException("Stream could not be decoded. Image size: " + size);
|
||||
}
|
||||
|
||||
mImageOK = true;
|
||||
resetInputRect();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void recycle() {
|
||||
if (mRegionDecoder != null) {
|
||||
mRegionDecoder.recycle();
|
||||
mRegionDecoder = null;
|
||||
}
|
||||
if (mCacheBitmap != null) {
|
||||
mCacheBitmap.recycle();
|
||||
mCacheBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void scaleImage(float scaleFactor) {
|
||||
if (!mImageOK)
|
||||
return;
|
||||
float newW = mInputRect.width() / scaleFactor;
|
||||
float newH = mInputRect.height() / scaleFactor;
|
||||
float dx = 0.5f * (mInputRect.width() - newW);
|
||||
float dy = 0.5f * (mInputRect.height() - newH);
|
||||
float max = 2.0f * Math.max(mImageWidth, mImageHeight);
|
||||
if (Math.min(newW, newH) >= 4.0f && Math.max(newW, newH) <= max) {
|
||||
mInputRect.inset(dx, dy);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void moveImage(float distanceX, float distanceY) {
|
||||
if (!mImageOK)
|
||||
return;
|
||||
float dx = (mInputRect.width() * distanceX) / mOutputRect.width();
|
||||
float dy = (mInputRect.height() * distanceY) / mOutputRect.height();
|
||||
dx = Math.max(mInputRect.width() * 0.1f, mInputRect.right + dx) - mInputRect.right;
|
||||
dy = Math.max(mInputRect.height() * 0.1f, mInputRect.bottom + dy) - mInputRect.bottom;
|
||||
dx = Math.min(mImageWidth - mInputRect.width() * 0.1f, mInputRect.left + dx) - mInputRect.left;
|
||||
dy = Math.min(mImageHeight - mInputRect.height() * 0.1f, mInputRect.top + dy) - mInputRect.top;
|
||||
mInputRect.offset(dx, dy);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent e) {
|
||||
boolean consumed = false;
|
||||
if (mLongPress) {
|
||||
switch (e.getAction()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mLabelCollection.moveLabel(e.getX(), e.getY());
|
||||
invalidate();
|
||||
consumed = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mLabelCollection.moveLabelEnd();
|
||||
invalidate();
|
||||
mLongPress = false;
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
consumed = mScaleDetector.onTouchEvent(e) || consumed;
|
||||
return mDetectorCompat.onTouchEvent(e) || consumed || super.onTouchEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
|
||||
super.onSizeChanged(w, h, old_w, old_h);
|
||||
if (mModeSize != null)
|
||||
mOutputRect = Utility.getEmbeddedRect(w, h, mModeSize.width(), mModeSize.height());
|
||||
mLabelCollection.update(w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
if (mImageOK) {
|
||||
maximizeImageToCanvasRect();
|
||||
adjustCanvasAndImageRect(getWidth(), getHeight());
|
||||
canvas.drawRect(mOutputRect, mBorderPaint);
|
||||
drawBitmap(canvas);
|
||||
}
|
||||
mLabelCollection.draw(canvas);
|
||||
drawModeRect(canvas);
|
||||
}
|
||||
|
||||
private void maximizeImageToCanvasRect() {
|
||||
mImageDrawRect.left = Math.round(mInputRect.left - mOutputRect.left * mInputRect.width() / mOutputRect.width());
|
||||
mImageDrawRect.top = Math.round(mInputRect.top - mOutputRect.top * mInputRect.height() / mOutputRect.height());
|
||||
mImageDrawRect.right = Math.round(mInputRect.right - (mOutputRect.right - getWidth()) * mInputRect.width() / mOutputRect.width());
|
||||
mImageDrawRect.bottom = Math.round(mInputRect.bottom - (mOutputRect.bottom - getHeight()) * mInputRect.height() / mOutputRect.height());
|
||||
}
|
||||
|
||||
private void adjustCanvasAndImageRect(int width, int height) {
|
||||
mCanvasDrawRect.set(0, 0, width, height);
|
||||
if (mImageDrawRect.left < 0) {
|
||||
mCanvasDrawRect.left -= (mImageDrawRect.left * mCanvasDrawRect.width()) / mImageDrawRect.width();
|
||||
mImageDrawRect.left = 0;
|
||||
}
|
||||
if (mImageDrawRect.top < 0) {
|
||||
mCanvasDrawRect.top -= (mImageDrawRect.top * mCanvasDrawRect.height()) / mImageDrawRect.height();
|
||||
mImageDrawRect.top = 0;
|
||||
}
|
||||
if (mImageDrawRect.right > mImageWidth) {
|
||||
mCanvasDrawRect.right -= ((mImageDrawRect.right - mImageWidth) * mCanvasDrawRect.width()) / mImageDrawRect.width();
|
||||
mImageDrawRect.right = mImageWidth;
|
||||
}
|
||||
if (mImageDrawRect.bottom > mImageHeight) {
|
||||
mCanvasDrawRect.bottom -= ((mImageDrawRect.bottom - mImageHeight) * mCanvasDrawRect.height()) / mImageDrawRect.height();
|
||||
mImageDrawRect.bottom = mImageHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawModeRect(Canvas canvas) {
|
||||
mRectPaint.setColor(Color.BLUE);
|
||||
canvas.drawRect(mOutputRect, mRectPaint);
|
||||
mRectPaint.setColor(Color.GREEN);
|
||||
drawRectInset(canvas, mOutputRect, -2);
|
||||
mRectPaint.setColor(Color.RED);
|
||||
drawRectInset(canvas, mOutputRect, -4);
|
||||
}
|
||||
|
||||
private void drawRectInset(Canvas canvas, Rect rect, int inset) {
|
||||
canvas.drawRect(rect.left + inset, rect.top + inset, rect.right - inset, rect.bottom - inset, mRectPaint);
|
||||
}
|
||||
|
||||
private Rect getIntRect(RectF rect) {
|
||||
return new Rect(Math.round(rect.left), Math.round(rect.top), Math.round(rect.right), Math.round(rect.bottom));
|
||||
}
|
||||
|
||||
private int getSampleSize() {
|
||||
int sx = Math.round(mInputRect.width() / mModeSize.width());
|
||||
int sy = Math.round(mInputRect.height() / mModeSize.height());
|
||||
int scale = Math.max(1, Math.max(sx, sy));
|
||||
return Integer.highestOneBit(scale);
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
if (!mImageOK)
|
||||
return null;
|
||||
|
||||
Bitmap result = Bitmap.createBitmap(mModeSize.width(), mModeSize.height(), Bitmap.Config.ARGB_8888);
|
||||
mImageDrawRect.set(getIntRect(mInputRect));
|
||||
adjustCanvasAndImageRect(mModeSize.width(), mModeSize.height());
|
||||
|
||||
Canvas canvas = new Canvas(result);
|
||||
canvas.drawColor(Color.BLACK);
|
||||
drawBitmap(canvas);
|
||||
mLabelCollection.draw(canvas, mOutputRect, new Rect(0, 0, mModeSize.width(), mModeSize.height()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void drawBitmap(Canvas canvas) {
|
||||
int w = mImageWidth;
|
||||
int h = mImageHeight;
|
||||
for (int i = 0; i < mOrientation / 90; ++i) {
|
||||
int tmp = w;
|
||||
w = h;
|
||||
h = tmp;
|
||||
mImageDrawRect.set(mImageDrawRect.top, h - mImageDrawRect.left, mImageDrawRect.bottom, h - mImageDrawRect.right);
|
||||
mCanvasDrawRect.set(mCanvasDrawRect.top, -mCanvasDrawRect.right, mCanvasDrawRect.bottom, -mCanvasDrawRect.left);
|
||||
}
|
||||
mImageDrawRect.sort();
|
||||
canvas.save();
|
||||
canvas.rotate(mOrientation);
|
||||
if (!mSmallImage) {
|
||||
int sampleSize = getSampleSize();
|
||||
if (sampleSize < mCacheSampleSize || !mCacheRect.contains(mImageDrawRect)) {
|
||||
if (mCacheBitmap != null)
|
||||
mCacheBitmap.recycle();
|
||||
int cacheWidth = mImageDrawRect.width();
|
||||
int cacheHeight = mImageDrawRect.height();
|
||||
while (cacheWidth * cacheHeight < (sampleSize * 1024 * sampleSize * 1024)) {
|
||||
cacheWidth += mImageDrawRect.width();
|
||||
cacheHeight += mImageDrawRect.height();
|
||||
}
|
||||
mCacheRect.set(
|
||||
Math.max(0, ~(sampleSize - 1) & (mImageDrawRect.centerX() - cacheWidth / 2)),
|
||||
Math.max(0, ~(sampleSize - 1) & (mImageDrawRect.centerY() - cacheHeight / 2)),
|
||||
Math.min(mRegionDecoder.getWidth(), ~(sampleSize - 1) & (mImageDrawRect.centerX() + cacheWidth / 2 + sampleSize - 1)),
|
||||
Math.min(mRegionDecoder.getHeight(), ~(sampleSize - 1) & (mImageDrawRect.centerY() + cacheHeight / 2 + sampleSize - 1)));
|
||||
mBitmapOptions.inSampleSize = mCacheSampleSize = sampleSize;
|
||||
mCacheBitmap = mRegionDecoder.decodeRegion(mCacheRect, mBitmapOptions);
|
||||
}
|
||||
mImageDrawRect.offset(-mCacheRect.left, -mCacheRect.top);
|
||||
mImageDrawRect.left /= mCacheSampleSize;
|
||||
mImageDrawRect.top /= mCacheSampleSize;
|
||||
mImageDrawRect.right /= mCacheSampleSize;
|
||||
mImageDrawRect.bottom /= mCacheSampleSize;
|
||||
}
|
||||
canvas.drawBitmap(mCacheBitmap, mImageDrawRect, mCanvasDrawRect, mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void editLabelBegin(float x, float y) {
|
||||
Label label = mLabelCollection.editLabelBegin(x, y);
|
||||
((MainActivity) getContext()).startEditTextActivity(label);
|
||||
}
|
||||
|
||||
public void editLabelEnd(Label label) {
|
||||
mLabelCollection.editLabelEnd(label);
|
||||
}
|
||||
|
||||
public LabelCollection getLabels() {
|
||||
return mLabelCollection;
|
||||
}
|
||||
}
|
||||
122
app/src/main/java/om/sstvencoder/EditTextActivity.java
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import om.sstvencoder.ColorPalette.ColorPaletteView;
|
||||
import om.sstvencoder.TextOverlay.Label;
|
||||
|
||||
public class EditTextActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
|
||||
public static final int REQUEST_CODE = 101;
|
||||
public static final String EXTRA = "EDIT_TEXT_EXTRA";
|
||||
private EditText mEditText;
|
||||
private ColorPaletteView mColorPaletteView;
|
||||
private float mTextSize;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_edit_text);
|
||||
mEditText = (EditText) findViewById(R.id.edit_text);
|
||||
mColorPaletteView = (ColorPaletteView) findViewById(R.id.edit_color);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
Label label = (Label) getIntent().getSerializableExtra(EXTRA);
|
||||
mEditText.setText(label.getText());
|
||||
mTextSize = label.getTextSize();
|
||||
initTextSizeSpinner(textSizeToPosition(mTextSize));
|
||||
((CheckBox) findViewById(R.id.edit_italic)).setChecked(label.getItalic());
|
||||
((CheckBox) findViewById(R.id.edit_bold)).setChecked(label.getBold());
|
||||
mColorPaletteView.setColor(label.getForeColor());
|
||||
}
|
||||
|
||||
private void initTextSizeSpinner(int position) {
|
||||
Spinner editTextSize = (Spinner) findViewById(R.id.edit_text_size);
|
||||
editTextSize.setOnItemSelectedListener(this);
|
||||
String[] textSizeList = new String[]{"Small", "Normal", "Large", "Huge"};
|
||||
editTextSize.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, textSizeList));
|
||||
editTextSize.setSelection(position);
|
||||
}
|
||||
|
||||
private int textSizeToPosition(float textSize) {
|
||||
int position = (int) (textSize - 1f);
|
||||
if (0 <= position && position <= 3)
|
||||
return position;
|
||||
return 1;
|
||||
}
|
||||
|
||||
private float positionToTextSize(int position) {
|
||||
return position + 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mTextSize = positionToTextSize(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_edit_text, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_done:
|
||||
done();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void done() {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA, getLabel());
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Label getLabel() {
|
||||
Label label = new Label();
|
||||
label.setText(mEditText.getText().toString());
|
||||
label.setTextSize(mTextSize);
|
||||
label.setItalic(((CheckBox) findViewById(R.id.edit_italic)).isChecked());
|
||||
label.setBold(((CheckBox) findViewById(R.id.edit_bold)).isChecked());
|
||||
label.setForeColor(mColorPaletteView.getColor());
|
||||
return label;
|
||||
}
|
||||
}
|
||||
137
app/src/main/java/om/sstvencoder/Encoder.java
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.IMode;
|
||||
import om.sstvencoder.ModeInterfaces.IModeInfo;
|
||||
import om.sstvencoder.Modes.ModeFactory;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.Output.OutputFactory;
|
||||
|
||||
// Creates IMode instance
|
||||
class Encoder {
|
||||
private final Thread mThread;
|
||||
private final List<IMode> mQueue;
|
||||
private boolean mQuit, mStop;
|
||||
private Class<?> mModeClass;
|
||||
|
||||
Encoder() {
|
||||
mQueue = new LinkedList<>();
|
||||
mQuit = false;
|
||||
mStop = false;
|
||||
mModeClass = ModeFactory.getDefaultMode();
|
||||
|
||||
mThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
IMode mode;
|
||||
synchronized (this) {
|
||||
while (mQueue.isEmpty() && !mQuit) {
|
||||
try {
|
||||
wait();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
if (mQuit)
|
||||
return;
|
||||
|
||||
mStop = false;
|
||||
mode = mQueue.remove(0);
|
||||
}
|
||||
mode.init();
|
||||
|
||||
while (mode.process()) {
|
||||
synchronized (this) {
|
||||
if (mQuit || mStop)
|
||||
break;
|
||||
}
|
||||
}
|
||||
mode.finish(mStop);
|
||||
}
|
||||
}
|
||||
};
|
||||
mThread.start();
|
||||
}
|
||||
|
||||
boolean setMode(String className) {
|
||||
try {
|
||||
mModeClass = Class.forName(className);
|
||||
} catch (Exception ignore) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IModeInfo getModeInfo() {
|
||||
return ModeFactory.getModeInfo(mModeClass);
|
||||
}
|
||||
|
||||
IModeInfo[] getModeInfoList() {
|
||||
return ModeFactory.getModeInfoList();
|
||||
}
|
||||
|
||||
void play(Bitmap bitmap) {
|
||||
IOutput output = OutputFactory.createOutputForSending();
|
||||
IMode mode = ModeFactory.CreateMode(mModeClass, bitmap, output);
|
||||
if (mode != null)
|
||||
enqueue(mode);
|
||||
}
|
||||
|
||||
boolean save(Bitmap bitmap, File file) {
|
||||
IOutput output = OutputFactory.createOutputForSavingAsWave(file);
|
||||
IMode mode = ModeFactory.CreateMode(mModeClass, bitmap, output);
|
||||
if (mode != null) {
|
||||
mode.init();
|
||||
|
||||
while (mode.process()) {
|
||||
if (mQuit)
|
||||
break;
|
||||
}
|
||||
mode.finish(mQuit);
|
||||
}
|
||||
return !mQuit;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
synchronized (mThread) {
|
||||
mStop = true;
|
||||
int size = mQueue.size();
|
||||
for (int i = 0; i < size; ++i)
|
||||
mQueue.remove(0).finish(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void enqueue(IMode mode) {
|
||||
synchronized (mThread) {
|
||||
mQueue.add(mode);
|
||||
mThread.notify();
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
synchronized (mThread) {
|
||||
mQuit = true;
|
||||
mThread.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
334
app/src/main/java/om/sstvencoder/MainActivity.java
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.IModeInfo;
|
||||
import om.sstvencoder.TextOverlay.Label;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String CLASS_NAME = "ClassName";
|
||||
private static final int REQUEST_IMAGE_CAPTURE = 1;
|
||||
private static final int REQUEST_PICK_IMAGE = 2;
|
||||
private Settings mSettings;
|
||||
private TextOverlayTemplate mTextOverlayTemplate;
|
||||
private CropView mCropView;
|
||||
private Encoder mEncoder;
|
||||
private File mFile;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
mCropView = (CropView) findViewById(R.id.cropView);
|
||||
mEncoder = new Encoder();
|
||||
IModeInfo mode = mEncoder.getModeInfo();
|
||||
mCropView.setModeSize(mode.getModeSize());
|
||||
setTitle(mode.getModeName());
|
||||
mSettings = new Settings(this);
|
||||
mSettings.load();
|
||||
mTextOverlayTemplate = new TextOverlayTemplate();
|
||||
mTextOverlayTemplate.load(mCropView.getLabels(), mSettings.getTextOverlayFile());
|
||||
loadImage(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
loadImage(intent);
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
|
||||
private void loadImage(Intent intent) {
|
||||
Uri uri = getImageUriFromIntent(intent);
|
||||
boolean verbose = true;
|
||||
if (uri == null) {
|
||||
uri = mSettings.getImageUri();
|
||||
verbose = false;
|
||||
}
|
||||
if (loadImage(uri, verbose))
|
||||
mSettings.setImageUri(uri);
|
||||
}
|
||||
|
||||
private Uri getImageUriFromIntent(Intent intent) {
|
||||
Uri uri = null;
|
||||
if (isIntentTypeValid(intent.getType()) && isIntentActionValid(intent.getAction())) {
|
||||
uri = intent.hasExtra(Intent.EXTRA_STREAM) ?
|
||||
(Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM) : intent.getData();
|
||||
if (uri == null) {
|
||||
String s = getString(R.string.load_img_err_txt_unsupported);
|
||||
showErrorMessage(getString(R.string.load_img_err_title), s, s + "\n\n" + intent);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
// Set verbose to false for any Uri that might have expired (e.g. shared from browser).
|
||||
private boolean loadImage(Uri uri, boolean verbose) {
|
||||
if (uri != null) {
|
||||
try {
|
||||
ContentResolver resolver = getContentResolver();
|
||||
mCropView.setBitmapStream(resolver.openInputStream(uri));
|
||||
mCropView.rotateImage(getOrientation(resolver, uri));
|
||||
return true;
|
||||
} catch (FileNotFoundException ex) {
|
||||
if (ex.getCause() instanceof ErrnoException
|
||||
&& ((ErrnoException) ex.getCause()).errno == OsConstants.EACCES) {
|
||||
requestPermissions();
|
||||
} else if (verbose) {
|
||||
String s = getString(R.string.load_img_err_title) + ": \n" + ex.getMessage();
|
||||
Toast.makeText(this, s, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (verbose) {
|
||||
String s = Utility.createMessage(ex) + "\n\n" + uri;
|
||||
showErrorMessage(getString(R.string.load_img_err_title), ex.getMessage(), s);
|
||||
}
|
||||
}
|
||||
}
|
||||
mCropView.setNoBitmap();
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isIntentActionValid(String action) {
|
||||
return Intent.ACTION_SEND.equals(action);
|
||||
}
|
||||
|
||||
private boolean isIntentTypeValid(String type) {
|
||||
return type != null && type.startsWith("image/");
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private void requestPermissions() {
|
||||
if (Build.VERSION_CODES.JELLY_BEAN > Build.VERSION.SDK_INT)
|
||||
return;
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
|
||||
private void showErrorMessage(final String title, final String shortText, final String longText) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(shortText);
|
||||
builder.setNeutralButton(getString(R.string.btn_ok), null);
|
||||
builder.setPositiveButton(getString(R.string.btn_send_email), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String device = Build.MANUFACTURER + ", " + Build.BRAND + ", " + Build.MODEL + ", " + Build.VERSION.RELEASE;
|
||||
String text = longText + "\n\n" + BuildConfig.VERSION_NAME + ", " + device;
|
||||
Intent intent = Utility.createEmailIntent(getString(R.string.email_subject), text);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.chooser_title)));
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showOrientationErrorMessage(Uri uri, Exception ex) {
|
||||
String title = getString(R.string.load_img_orientation_err_title);
|
||||
String longText = title + "\n\n" + Utility.createMessage(ex) + "\n\n" + uri;
|
||||
showErrorMessage(title, ex.getMessage(), longText);
|
||||
}
|
||||
|
||||
public int getOrientation(ContentResolver resolver, Uri uri) {
|
||||
int orientation = 0;
|
||||
try {
|
||||
Cursor cursor = resolver.query(uri, new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
|
||||
if (cursor.moveToFirst())
|
||||
orientation = cursor.getInt(0);
|
||||
cursor.close();
|
||||
} catch (Exception ignore) {
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(uri.getPath());
|
||||
orientation = Utility.convertToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0));
|
||||
} catch (Exception ex) {
|
||||
showOrientationErrorMessage(uri, ex);
|
||||
}
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
createModesMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void createModesMenu(Menu menu) {
|
||||
SubMenu modesSubMenu = menu.findItem(R.id.action_modes).getSubMenu();
|
||||
IModeInfo[] modeInfoList = mEncoder.getModeInfoList();
|
||||
for (IModeInfo modeInfo : modeInfoList) {
|
||||
MenuItem item = modesSubMenu.add(modeInfo.getModeName());
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CLASS_NAME, modeInfo.getModeClassName());
|
||||
item.setIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_pick_picture:
|
||||
dispatchPickPictureIntent();
|
||||
return true;
|
||||
case R.id.action_take_picture:
|
||||
dispatchTakePictureIntent();
|
||||
return true;
|
||||
case R.id.action_save_wave:
|
||||
save();
|
||||
return true;
|
||||
case R.id.action_play:
|
||||
play();
|
||||
return true;
|
||||
case R.id.action_stop:
|
||||
mEncoder.stop();
|
||||
return true;
|
||||
case R.id.action_rotate:
|
||||
mCropView.rotateImage(90);
|
||||
return true;
|
||||
case R.id.action_modes:
|
||||
return true;
|
||||
default:
|
||||
String className = item.getIntent().getStringExtra(CLASS_NAME);
|
||||
if (mEncoder.setMode(className)) {
|
||||
IModeInfo modeInfo = mEncoder.getModeInfo();
|
||||
mCropView.setModeSize(modeInfo.getModeSize());
|
||||
setTitle(modeInfo.getModeName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void startEditTextActivity(@NonNull Label label) {
|
||||
Intent intent = new Intent(this, EditTextActivity.class);
|
||||
intent.putExtra(EditTextActivity.EXTRA, label);
|
||||
startActivityForResult(intent, EditTextActivity.REQUEST_CODE);
|
||||
}
|
||||
|
||||
private void dispatchTakePictureIntent() {
|
||||
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
|
||||
Toast.makeText(this, "Device has no camera.", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
mFile = Utility.createImageFilePath();
|
||||
if (mFile != null) {
|
||||
Uri uri = FileProvider.getUriForFile(this, "om.sstvencoder", mFile);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
|
||||
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchPickPictureIntent() {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
if (intent.resolveActivity(getPackageManager()) != null)
|
||||
startActivityForResult(intent, REQUEST_PICK_IMAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case EditTextActivity.REQUEST_CODE:
|
||||
Label label = null;
|
||||
if (resultCode == RESULT_OK && data != null)
|
||||
label = (Label) data.getSerializableExtra(EditTextActivity.EXTRA);
|
||||
mCropView.editLabelEnd(label);
|
||||
break;
|
||||
case REQUEST_IMAGE_CAPTURE:
|
||||
if (resultCode == RESULT_OK) {
|
||||
Uri uri = Uri.fromFile(mFile);
|
||||
if (loadImage(uri, true)) {
|
||||
mSettings.setImageUri(uri);
|
||||
addImageToGallery(uri);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REQUEST_PICK_IMAGE:
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
Uri uri = data.getData();
|
||||
if (loadImage(uri, true))
|
||||
mSettings.setImageUri(uri);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void addImageToGallery(Uri uri) {
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(uri);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void play() {
|
||||
mEncoder.play(mCropView.getBitmap());
|
||||
}
|
||||
|
||||
private void save() {
|
||||
File file = Utility.createWaveFilePath();
|
||||
if (mEncoder.save(mCropView.getBitmap(), file)) {
|
||||
ContentValues values = Utility.getWavContentValues(file);
|
||||
Uri uri = MediaStore.Audio.Media.getContentUriForPath(file.getAbsolutePath());
|
||||
getContentResolver().insert(uri, values);
|
||||
Toast.makeText(this, file.getName() + " saved.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mSettings.save();
|
||||
mTextOverlayTemplate.save(mCropView.getLabels(), mSettings.getTextOverlayFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mEncoder.destroy();
|
||||
}
|
||||
}
|
||||
24
app/src/main/java/om/sstvencoder/ModeInterfaces/IMode.java
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ModeInterfaces;
|
||||
|
||||
public interface IMode {
|
||||
void init();
|
||||
|
||||
boolean process();
|
||||
|
||||
void finish(boolean cancel);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ModeInterfaces;
|
||||
|
||||
public interface IModeInfo {
|
||||
int getModeName();
|
||||
|
||||
String getModeClassName();
|
||||
|
||||
ModeSize getModeSize();
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.ModeInterfaces;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ModeSize {
|
||||
int width();
|
||||
|
||||
int height();
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
class NV21 extends Yuv {
|
||||
NV21(Bitmap bitmap) {
|
||||
super(bitmap);
|
||||
}
|
||||
|
||||
protected void convertBitmapToYuv(Bitmap bitmap) {
|
||||
mYuv = new byte[(3 * mWidth * mHeight) / 2];
|
||||
int pos = 0;
|
||||
|
||||
for (int h = 0; h < mHeight; ++h)
|
||||
for (int w = 0; w < mWidth; ++w)
|
||||
mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
|
||||
|
||||
for (int h = 0; h < mHeight; h += 2) {
|
||||
for (int w = 0; w < mWidth; w += 2) {
|
||||
int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
|
||||
int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
|
||||
int v2 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
|
||||
int v3 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h + 1));
|
||||
mYuv[pos++] = (byte) ((v0 + v1 + v2 + v3) / 4);
|
||||
int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
|
||||
int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
|
||||
int u2 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
|
||||
int u3 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h + 1));
|
||||
mYuv[pos++] = (byte) ((u0 + u1 + u2 + u3) / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getY(int x, int y) {
|
||||
return 255 & mYuv[mWidth * y + x];
|
||||
}
|
||||
|
||||
public int getU(int x, int y) {
|
||||
return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + (x | 1)];
|
||||
}
|
||||
|
||||
public int getV(int x, int y) {
|
||||
return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + (x & ~1)];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
class YUV440P extends Yuv {
|
||||
YUV440P(Bitmap bitmap) {
|
||||
super(bitmap);
|
||||
}
|
||||
|
||||
protected void convertBitmapToYuv(Bitmap bitmap) {
|
||||
mYuv = new byte[2 * mWidth * mHeight];
|
||||
int pos = 0;
|
||||
|
||||
for (int h = 0; h < mHeight; ++h)
|
||||
for (int w = 0; w < mWidth; ++w)
|
||||
mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
|
||||
|
||||
for (int h = 0; h < mHeight; h += 2) {
|
||||
for (int w = 0; w < mWidth; ++w) {
|
||||
int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
|
||||
int u1 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
|
||||
mYuv[pos++] = (byte) ((u0 + u1) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
for (int h = 0; h < mHeight; h += 2) {
|
||||
for (int w = 0; w < mWidth; ++w) {
|
||||
int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
|
||||
int v1 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
|
||||
mYuv[pos++] = (byte) ((v0 + v1) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getY(int x, int y) {
|
||||
return 255 & mYuv[mWidth * y + x];
|
||||
}
|
||||
|
||||
public int getU(int x, int y) {
|
||||
return 255 & mYuv[mWidth * mHeight + mWidth * (y >> 1) + x];
|
||||
}
|
||||
|
||||
public int getV(int x, int y) {
|
||||
return 255 & mYuv[((3 * mWidth * mHeight) >> 1) + mWidth * (y >> 1) + x];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
class YUY2 extends Yuv {
|
||||
YUY2(Bitmap bitmap) {
|
||||
super(bitmap);
|
||||
}
|
||||
|
||||
protected void convertBitmapToYuv(Bitmap bitmap) {
|
||||
mYuv = new byte[2 * mWidth * mHeight];
|
||||
|
||||
for (int pos = 0, h = 0; h < mHeight; ++h) {
|
||||
for (int w = 0; w < mWidth; w += 2) {
|
||||
mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
|
||||
int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
|
||||
int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
|
||||
mYuv[pos++] = (byte) ((u0 + u1) / 2);
|
||||
mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w + 1, h));
|
||||
int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
|
||||
int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
|
||||
mYuv[pos++] = (byte) ((v0 + v1) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getY(int x, int y) {
|
||||
return 255 & mYuv[2 * mWidth * y + 2 * x];
|
||||
}
|
||||
|
||||
public int getU(int x, int y) {
|
||||
return 255 & mYuv[2 * mWidth * y + (((x & ~1) << 1) | 1)];
|
||||
}
|
||||
|
||||
public int getV(int x, int y) {
|
||||
return 255 & mYuv[2 * mWidth * y + ((x << 1) | 3)];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
class YV12 extends Yuv {
|
||||
YV12(Bitmap bitmap) {
|
||||
super(bitmap);
|
||||
}
|
||||
|
||||
protected void convertBitmapToYuv(Bitmap bitmap) {
|
||||
mYuv = new byte[(3 * mWidth * mHeight) / 2];
|
||||
int pos = 0;
|
||||
|
||||
for (int h = 0; h < mHeight; ++h)
|
||||
for (int w = 0; w < mWidth; ++w)
|
||||
mYuv[pos++] = (byte) YuvConverter.convertToY(bitmap.getPixel(w, h));
|
||||
|
||||
for (int h = 0; h < mHeight; h += 2) {
|
||||
for (int w = 0; w < mWidth; w += 2) {
|
||||
int u0 = YuvConverter.convertToU(bitmap.getPixel(w, h));
|
||||
int u1 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h));
|
||||
int u2 = YuvConverter.convertToU(bitmap.getPixel(w, h + 1));
|
||||
int u3 = YuvConverter.convertToU(bitmap.getPixel(w + 1, h + 1));
|
||||
mYuv[pos++] = (byte) ((u0 + u1 + u2 + u3) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
for (int h = 0; h < mHeight; h += 2) {
|
||||
for (int w = 0; w < mWidth; w += 2) {
|
||||
int v0 = YuvConverter.convertToV(bitmap.getPixel(w, h));
|
||||
int v1 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h));
|
||||
int v2 = YuvConverter.convertToV(bitmap.getPixel(w, h + 1));
|
||||
int v3 = YuvConverter.convertToV(bitmap.getPixel(w + 1, h + 1));
|
||||
mYuv[pos++] = (byte) ((v0 + v1 + v2 + v3) / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getY(int x, int y) {
|
||||
return 255 & mYuv[mWidth * y + x];
|
||||
}
|
||||
|
||||
public int getU(int x, int y) {
|
||||
return 255 & mYuv[mWidth * mHeight + (mWidth >> 1) * (y >> 1) + (x >> 1)];
|
||||
}
|
||||
|
||||
public int getV(int x, int y) {
|
||||
return 255 & mYuv[((5 * mWidth * mHeight) >> 2) + (mWidth >> 1) * (y >> 1) + (x >> 1)];
|
||||
}
|
||||
}
|
||||
46
app/src/main/java/om/sstvencoder/Modes/ImageFormats/Yuv.java
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public abstract class Yuv {
|
||||
protected byte[] mYuv;
|
||||
final int mWidth;
|
||||
final int mHeight;
|
||||
|
||||
Yuv(Bitmap bitmap) {
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
convertBitmapToYuv(bitmap);
|
||||
}
|
||||
|
||||
protected abstract void convertBitmapToYuv(Bitmap bitmap);
|
||||
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public abstract int getY(int x, int y);
|
||||
|
||||
public abstract int getU(int x, int y);
|
||||
|
||||
public abstract int getV(int x, int y);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
final class YuvConverter {
|
||||
static int convertToY(int color) {
|
||||
double R = Color.red(color);
|
||||
double G = Color.green(color);
|
||||
double B = Color.blue(color);
|
||||
return clamp(16.0 + (.003906 * ((65.738 * R) + (129.057 * G) + (25.064 * B))));
|
||||
}
|
||||
|
||||
static int convertToU(int color) {
|
||||
double R = Color.red(color);
|
||||
double G = Color.green(color);
|
||||
double B = Color.blue(color);
|
||||
return clamp(128.0 + (.003906 * ((-37.945 * R) + (-74.494 * G) + (112.439 * B))));
|
||||
}
|
||||
|
||||
static int convertToV(int color) {
|
||||
double R = Color.red(color);
|
||||
double G = Color.green(color);
|
||||
double B = Color.blue(color);
|
||||
return clamp(128.0 + (.003906 * ((112.439 * R) + (-94.154 * G) + (-18.285 * B))));
|
||||
}
|
||||
|
||||
private static int clamp(double value) {
|
||||
return value < 0.0 ? 0 : (value > 255.0 ? 255 : (int) value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public final class YuvFactory {
|
||||
public static Yuv createYuv(Bitmap bitmap, int format) {
|
||||
switch (format) {
|
||||
case YuvImageFormat.YV12:
|
||||
return new YV12(bitmap);
|
||||
case YuvImageFormat.NV21:
|
||||
return new NV21(bitmap);
|
||||
case YuvImageFormat.YUY2:
|
||||
return new YUY2(bitmap);
|
||||
case YuvImageFormat.YUV440P:
|
||||
return new YUV440P(bitmap);
|
||||
default:
|
||||
throw new IllegalArgumentException("Only support YV12, NV21, YUY2 and YUV440P");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes.ImageFormats;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
public class YuvImageFormat extends ImageFormat {
|
||||
public static final int YUV440P = 0x50303434;
|
||||
}
|
||||
100
app/src/main/java/om/sstvencoder/Modes/Martin.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
|
||||
abstract class Martin extends Mode {
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mSyncPorchSamples;
|
||||
private final double mSyncPorchFrequency;
|
||||
|
||||
private final int mSeparatorSamples;
|
||||
private final double mSeparatorFrequency;
|
||||
|
||||
protected double mColorScanDurationMs;
|
||||
protected int mColorScanSamples;
|
||||
|
||||
Martin(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(4.862);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mSyncPorchSamples = convertMsToSamples(0.572);
|
||||
mSyncPorchFrequency = 1500.0;
|
||||
|
||||
mSeparatorSamples = convertMsToSamples(0.572);
|
||||
mSeparatorFrequency = 1500.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = mSyncPulseSamples + mSyncPorchSamples
|
||||
+ 3 * (mSeparatorSamples + mColorScanSamples);
|
||||
return mBitmap.getHeight() * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
addSyncPulse();
|
||||
addSyncPorch();
|
||||
addGreenScan(mLine);
|
||||
addSeparator();
|
||||
addBlueScan(mLine);
|
||||
addSeparator();
|
||||
addRedScan(mLine);
|
||||
addSeparator();
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addSyncPorch() {
|
||||
for (int i = 0; i < mSyncPorchSamples; ++i)
|
||||
setTone(mSyncPorchFrequency);
|
||||
}
|
||||
|
||||
private void addSeparator() {
|
||||
for (int i = 0; i < mSeparatorSamples; ++i)
|
||||
setTone(mSeparatorFrequency);
|
||||
}
|
||||
|
||||
private void addGreenScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.green(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addBlueScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.blue(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addRedScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.red(getColor(i, y)));
|
||||
}
|
||||
|
||||
private int getColor(int colorScanSample, int y) {
|
||||
int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
|
||||
return mBitmap.getPixel(x, y);
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/om/sstvencoder/Modes/Martin1.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_martin1)
|
||||
class Martin1 extends Martin {
|
||||
Martin1(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 44;
|
||||
mColorScanDurationMs = 146.432;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/om/sstvencoder/Modes/Martin2.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_martin2)
|
||||
class Martin2 extends Martin {
|
||||
Martin2(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 40;
|
||||
mColorScanDurationMs = 73.216;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
133
app/src/main/java/om/sstvencoder/Modes/Mode.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.IMode;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
|
||||
abstract class Mode implements IMode {
|
||||
protected Bitmap mBitmap;
|
||||
protected int mVISCode;
|
||||
protected int mLine;
|
||||
private IOutput mOutput;
|
||||
private double mSampleRate;
|
||||
private double mRunningIntegral;
|
||||
|
||||
protected Mode(Bitmap bitmap, IOutput output) {
|
||||
mOutput = output;
|
||||
mSampleRate = mOutput.getSampleRate();
|
||||
mBitmap = bitmap;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
mRunningIntegral = 0.0;
|
||||
mLine = 0;
|
||||
mOutput.init(getTotalSamples());
|
||||
writeCalibrationHeader();
|
||||
}
|
||||
|
||||
public boolean process() {
|
||||
if (mLine >= mBitmap.getHeight())
|
||||
return false;
|
||||
|
||||
writeEncodedLine();
|
||||
++mLine;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note that also Bitmap will be recycled here
|
||||
public void finish(boolean cancel) {
|
||||
mOutput.finish(cancel);
|
||||
destroyBitmap();
|
||||
}
|
||||
|
||||
private int getTotalSamples() {
|
||||
return getHeaderSamples() + getTransmissionSamples();
|
||||
}
|
||||
|
||||
private int getHeaderSamples() {
|
||||
return 2 * convertMsToSamples(300.0)
|
||||
+ convertMsToSamples(10.0)
|
||||
+ 10 * convertMsToSamples(30.0);
|
||||
}
|
||||
|
||||
protected abstract int getTransmissionSamples();
|
||||
|
||||
private void writeCalibrationHeader() {
|
||||
int leaderToneSamples = convertMsToSamples(300.0);
|
||||
double leaderToneFrequency = 1900.0;
|
||||
|
||||
int breakSamples = convertMsToSamples(10.0);
|
||||
double breakFrequency = 1200.0;
|
||||
|
||||
int visBitSamples = convertMsToSamples(30.0);
|
||||
double visBitSSFrequency = 1200.0;
|
||||
double[] visBitFrequency = new double[]{1300.0, 1100.0};
|
||||
|
||||
for (int i = 0; i < leaderToneSamples; ++i)
|
||||
setTone(leaderToneFrequency);
|
||||
|
||||
for (int i = 0; i < breakSamples; ++i)
|
||||
setTone(breakFrequency);
|
||||
|
||||
for (int i = 0; i < leaderToneSamples; ++i)
|
||||
setTone(leaderToneFrequency);
|
||||
|
||||
for (int i = 0; i < visBitSamples; ++i)
|
||||
setTone(visBitSSFrequency);
|
||||
|
||||
int parity = 0;
|
||||
for (int pos = 0; pos < 7; ++pos) {
|
||||
int bit = (mVISCode >> pos) & 1;
|
||||
parity ^= bit;
|
||||
for (int i = 0; i < visBitSamples; ++i)
|
||||
setTone(visBitFrequency[bit]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < visBitSamples; ++i)
|
||||
setTone(visBitFrequency[parity]);
|
||||
|
||||
for (int i = 0; i < visBitSamples; ++i)
|
||||
setTone(visBitSSFrequency);
|
||||
}
|
||||
|
||||
protected abstract void writeEncodedLine();
|
||||
|
||||
protected int convertMsToSamples(double durationMs) {
|
||||
return (int) Math.round(durationMs * mSampleRate / 1000.0);
|
||||
}
|
||||
|
||||
protected void setTone(double frequency) {
|
||||
mRunningIntegral += 2.0 * frequency * Math.PI / mSampleRate;
|
||||
mRunningIntegral %= 2.0 * Math.PI;
|
||||
mOutput.write(Math.sin(mRunningIntegral));
|
||||
}
|
||||
|
||||
protected void setColorTone(int color) {
|
||||
double blackFrequency = 1500.0;
|
||||
double whiteFrequency = 2300.0;
|
||||
setTone(color * (whiteFrequency - blackFrequency) / 255.0 + blackFrequency);
|
||||
}
|
||||
|
||||
private void destroyBitmap() {
|
||||
if (mBitmap != null && !mBitmap.isRecycled()) {
|
||||
mBitmap.recycle();
|
||||
mBitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
app/src/main/java/om/sstvencoder/Modes/ModeDescription.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ModeDescription {
|
||||
int name();
|
||||
}
|
||||
74
app/src/main/java/om/sstvencoder/Modes/ModeFactory.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.IMode;
|
||||
import om.sstvencoder.ModeInterfaces.IModeInfo;
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
|
||||
public final class ModeFactory {
|
||||
public static Class<?> getDefaultMode() {
|
||||
return Robot36.class;
|
||||
}
|
||||
|
||||
public static IModeInfo[] getModeInfoList() {
|
||||
return new IModeInfo[]{
|
||||
new ModeInfo(Martin1.class), new ModeInfo(Martin2.class),
|
||||
new ModeInfo(PD50.class), new ModeInfo(PD90.class), new ModeInfo(PD120.class),
|
||||
new ModeInfo(PD160.class), new ModeInfo(PD180.class),
|
||||
new ModeInfo(PD240.class), new ModeInfo(PD290.class),
|
||||
new ModeInfo(Scottie1.class), new ModeInfo(Scottie2.class), new ModeInfo(ScottieDX.class),
|
||||
new ModeInfo(Robot36.class), new ModeInfo(Robot72.class),
|
||||
new ModeInfo(Wraase.class)
|
||||
};
|
||||
}
|
||||
|
||||
public static IModeInfo getModeInfo(Class<?> modeClass) {
|
||||
if (!isModeClassValid(modeClass))
|
||||
return null;
|
||||
|
||||
return new ModeInfo(modeClass);
|
||||
}
|
||||
|
||||
public static IMode CreateMode(Class<?> modeClass, Bitmap bitmap, IOutput output) {
|
||||
Mode mode = null;
|
||||
|
||||
if (bitmap != null && output != null && isModeClassValid(modeClass)) {
|
||||
ModeSize size = modeClass.getAnnotation(ModeSize.class);
|
||||
|
||||
if (bitmap.getWidth() == size.width() && bitmap.getHeight() == size.height()) {
|
||||
try {
|
||||
Constructor constructor = modeClass.getConstructor(Bitmap.class, IOutput.class);
|
||||
mode = (Mode) constructor.newInstance(bitmap, output);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
private static boolean isModeClassValid(Class<?> modeClass) {
|
||||
return Mode.class.isAssignableFrom(modeClass) &&
|
||||
modeClass.isAnnotationPresent(ModeSize.class) &&
|
||||
modeClass.isAnnotationPresent(ModeDescription.class);
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/om/sstvencoder/Modes/ModeInfo.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.IModeInfo;
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
|
||||
class ModeInfo implements IModeInfo {
|
||||
private final Class<?> mModeClass;
|
||||
|
||||
ModeInfo(Class<?> modeClass) {
|
||||
mModeClass = modeClass;
|
||||
}
|
||||
|
||||
public int getModeName() {
|
||||
return mModeClass.getAnnotation(ModeDescription.class).name();
|
||||
}
|
||||
|
||||
public String getModeClassName() {
|
||||
return mModeClass.getName();
|
||||
}
|
||||
|
||||
public ModeSize getModeSize() {
|
||||
return mModeClass.getAnnotation(ModeSize.class);
|
||||
}
|
||||
}
|
||||
87
app/src/main/java/om/sstvencoder/Modes/PD.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.Modes.ImageFormats.Yuv;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvFactory;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
|
||||
abstract class PD extends Mode {
|
||||
private final Yuv mYuv;
|
||||
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mPorchSamples;
|
||||
private final double mPorchFrequency;
|
||||
|
||||
protected double mColorScanDurationMs;
|
||||
protected int mColorScanSamples;
|
||||
|
||||
PD(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.YUV440P);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(20.0);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mPorchSamples = convertMsToSamples(2.08);
|
||||
mPorchFrequency = 1500.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = mSyncPulseSamples + mPorchSamples + 4 * mColorScanSamples;
|
||||
return mBitmap.getHeight() / 2 * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
addSyncPulse();
|
||||
addPorch();
|
||||
addYScan(mLine);
|
||||
addVScan(mLine);
|
||||
addUScan(mLine);
|
||||
addYScan(++mLine);
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addPorch() {
|
||||
for (int i = 0; i < mPorchSamples; ++i)
|
||||
setTone(mPorchFrequency);
|
||||
}
|
||||
|
||||
private void addYScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(mYuv.getY((i * mYuv.getWidth()) / mColorScanSamples, y));
|
||||
}
|
||||
|
||||
private void addUScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(mYuv.getU((i * mYuv.getWidth()) / mColorScanSamples, y));
|
||||
}
|
||||
|
||||
private void addVScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(mYuv.getV((i * mYuv.getWidth()) / mColorScanSamples, y));
|
||||
}
|
||||
}
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD120.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 640, height = 496)
|
||||
@ModeDescription(name = R.string.action_pd120)
|
||||
class PD120 extends PD {
|
||||
PD120(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 95;
|
||||
mColorScanDurationMs = 121.6;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD160.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 512, height = 400)
|
||||
@ModeDescription(name = R.string.action_pd160)
|
||||
class PD160 extends PD {
|
||||
PD160(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 98;
|
||||
mColorScanDurationMs = 195.584;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD180.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 640, height = 496)
|
||||
@ModeDescription(name = R.string.action_pd180)
|
||||
class PD180 extends PD {
|
||||
PD180(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 96;
|
||||
mColorScanDurationMs = 183.04;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD240.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 640, height = 496)
|
||||
@ModeDescription(name = R.string.action_pd240)
|
||||
class PD240 extends PD {
|
||||
PD240(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 97;
|
||||
mColorScanDurationMs = 244.48;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD290.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 800, height = 616)
|
||||
@ModeDescription(name = R.string.action_pd290)
|
||||
class PD290 extends PD {
|
||||
PD290(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 94;
|
||||
mColorScanDurationMs = 228.8;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD50.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_pd50)
|
||||
class PD50 extends PD {
|
||||
PD50(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 93;
|
||||
mColorScanDurationMs = 91.52;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/src/main/java/om/sstvencoder/Modes/PD90.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_pd90)
|
||||
class PD90 extends PD {
|
||||
PD90(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 99;
|
||||
mColorScanDurationMs = 170.24;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
129
app/src/main/java/om/sstvencoder/Modes/Robot36.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.Modes.ImageFormats.Yuv;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvFactory;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 240)
|
||||
@ModeDescription(name = R.string.action_robot36)
|
||||
class Robot36 extends Mode {
|
||||
private final Yuv mYuv;
|
||||
|
||||
private final int mLumaScanSamples;
|
||||
private final int mChrominanceScanSamples;
|
||||
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mSyncPorchSamples;
|
||||
private final double mSyncPorchFrequency;
|
||||
|
||||
private final int mPorchSamples;
|
||||
private final double mPorchFrequency;
|
||||
|
||||
private final int mSeparatorSamples;
|
||||
private final double mEvenSeparatorFrequency;
|
||||
private final double mOddSeparatorFrequency;
|
||||
|
||||
Robot36(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.NV21);
|
||||
mVISCode = 8;
|
||||
|
||||
mLumaScanSamples = convertMsToSamples(88.0);
|
||||
mChrominanceScanSamples = convertMsToSamples(44.0);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(9.0);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mSyncPorchSamples = convertMsToSamples(3.0);
|
||||
mSyncPorchFrequency = 1500.0;
|
||||
|
||||
mPorchSamples = convertMsToSamples(1.5);
|
||||
mPorchFrequency = 1900.0;
|
||||
|
||||
mSeparatorSamples = convertMsToSamples(4.5);
|
||||
mEvenSeparatorFrequency = 1500.0;
|
||||
mOddSeparatorFrequency = 2300.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = mSyncPulseSamples + mSyncPorchSamples
|
||||
+ mLumaScanSamples + mSeparatorSamples
|
||||
+ mPorchSamples + mChrominanceScanSamples;
|
||||
return mBitmap.getHeight() * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
addSyncPulse();
|
||||
addSyncPorch();
|
||||
addYScan(mLine);
|
||||
|
||||
if (mLine % 2 == 0) {
|
||||
addSeparator(mEvenSeparatorFrequency);
|
||||
addPorch();
|
||||
addVScan(mLine);
|
||||
} else {
|
||||
addSeparator(mOddSeparatorFrequency);
|
||||
addPorch();
|
||||
addUScan(mLine);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addSyncPorch() {
|
||||
for (int i = 0; i < mSyncPorchSamples; ++i)
|
||||
setTone(mSyncPorchFrequency);
|
||||
}
|
||||
|
||||
private void addSeparator(double separatorFrequency) {
|
||||
for (int i = 0; i < mSeparatorSamples; ++i)
|
||||
setTone(separatorFrequency);
|
||||
}
|
||||
|
||||
private void addPorch() {
|
||||
for (int i = 0; i < mPorchSamples; ++i)
|
||||
setTone(mPorchFrequency);
|
||||
}
|
||||
|
||||
private void addYScan(int y) {
|
||||
for (int i = 0; i < mLumaScanSamples; ++i)
|
||||
setColorTone(mYuv.getY((i * mYuv.getWidth()) / mLumaScanSamples, y));
|
||||
}
|
||||
|
||||
private void addUScan(int y) {
|
||||
for (int i = 0; i < mChrominanceScanSamples; ++i)
|
||||
setColorTone(mYuv.getU((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
|
||||
}
|
||||
|
||||
private void addVScan(int y) {
|
||||
for (int i = 0; i < mChrominanceScanSamples; ++i)
|
||||
setColorTone(mYuv.getV((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
|
||||
}
|
||||
}
|
||||
|
||||
123
app/src/main/java/om/sstvencoder/Modes/Robot72.java
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.Modes.ImageFormats.Yuv;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvFactory;
|
||||
import om.sstvencoder.Modes.ImageFormats.YuvImageFormat;
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 240)
|
||||
@ModeDescription(name = R.string.action_robot72)
|
||||
class Robot72 extends Mode {
|
||||
private final Yuv mYuv;
|
||||
|
||||
private final int mLumaScanSamples;
|
||||
private final int mChrominanceScanSamples;
|
||||
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mSyncPorchSamples;
|
||||
private final double mSyncPorchFrequency;
|
||||
|
||||
private final int mPorchSamples;
|
||||
private final double mPorchFrequency;
|
||||
|
||||
private final int mSeparatorSamples;
|
||||
private final double mFirstSeparatorFrequency;
|
||||
private final double mSecondSeparatorFrequency;
|
||||
|
||||
Robot72(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mYuv = YuvFactory.createYuv(mBitmap, YuvImageFormat.YUY2);
|
||||
mVISCode = 12;
|
||||
|
||||
mLumaScanSamples = convertMsToSamples(138.0);
|
||||
mChrominanceScanSamples = convertMsToSamples(69.0);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(9.0);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mSyncPorchSamples = convertMsToSamples(3.0);
|
||||
mSyncPorchFrequency = 1500.0;
|
||||
|
||||
mPorchSamples = convertMsToSamples(1.5);
|
||||
mPorchFrequency = 1900.0;
|
||||
|
||||
mSeparatorSamples = convertMsToSamples(4.5);
|
||||
mFirstSeparatorFrequency = 1500.0;
|
||||
mSecondSeparatorFrequency = 2300.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = mSyncPulseSamples + mSyncPorchSamples + mLumaScanSamples
|
||||
+ 2 * (mSeparatorSamples + mPorchSamples + mChrominanceScanSamples);
|
||||
return mBitmap.getHeight() * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
addSyncPulse();
|
||||
addSyncPorch();
|
||||
addYScan(mLine);
|
||||
addSeparator(mFirstSeparatorFrequency);
|
||||
addPorch();
|
||||
addVScan(mLine);
|
||||
addSeparator(mSecondSeparatorFrequency);
|
||||
addPorch();
|
||||
addUScan(mLine);
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addSyncPorch() {
|
||||
for (int i = 0; i < mSyncPorchSamples; ++i)
|
||||
setTone(mSyncPorchFrequency);
|
||||
}
|
||||
|
||||
private void addSeparator(double separatorFrequency) {
|
||||
for (int i = 0; i < mSeparatorSamples; ++i)
|
||||
setTone(separatorFrequency);
|
||||
}
|
||||
|
||||
private void addPorch() {
|
||||
for (int i = 0; i < mPorchSamples; ++i)
|
||||
setTone(mPorchFrequency);
|
||||
}
|
||||
|
||||
private void addYScan(int y) {
|
||||
for (int i = 0; i < mLumaScanSamples; ++i)
|
||||
setColorTone(mYuv.getY((i * mYuv.getWidth()) / mLumaScanSamples, y));
|
||||
}
|
||||
|
||||
private void addUScan(int y) {
|
||||
for (int i = 0; i < mChrominanceScanSamples; ++i)
|
||||
setColorTone(mYuv.getU((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
|
||||
}
|
||||
|
||||
private void addVScan(int y) {
|
||||
for (int i = 0; i < mChrominanceScanSamples; ++i)
|
||||
setColorTone(mYuv.getV((i * mYuv.getWidth()) / mChrominanceScanSamples, y));
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/om/sstvencoder/Modes/Scottie.java
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
|
||||
abstract class Scottie extends Mode {
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mSyncPorchSamples;
|
||||
private final double mSyncPorchFrequency;
|
||||
|
||||
private final int mSeparatorSamples;
|
||||
private final double mSeparatorFrequency;
|
||||
|
||||
protected double mColorScanDurationMs;
|
||||
protected int mColorScanSamples;
|
||||
|
||||
Scottie(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(9.0);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mSyncPorchSamples = convertMsToSamples(1.5);
|
||||
mSyncPorchFrequency = 1500.0;
|
||||
|
||||
mSeparatorSamples = convertMsToSamples(1.5);
|
||||
mSeparatorFrequency = 1500.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = 2 * mSeparatorSamples + 3 * mColorScanSamples +
|
||||
mSyncPulseSamples + mSyncPorchSamples;
|
||||
return mSyncPulseSamples + mBitmap.getHeight() * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
if (mLine == 0)
|
||||
addSyncPulse();
|
||||
|
||||
addSeparator();
|
||||
addGreenScan(mLine);
|
||||
addSeparator();
|
||||
addBlueScan(mLine);
|
||||
addSyncPulse();
|
||||
addSyncPorch();
|
||||
addRedScan(mLine);
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addSyncPorch() {
|
||||
for (int i = 0; i < mSyncPorchSamples; ++i)
|
||||
setTone(mSyncPorchFrequency);
|
||||
}
|
||||
|
||||
private void addSeparator() {
|
||||
for (int i = 0; i < mSeparatorSamples; ++i)
|
||||
setTone(mSeparatorFrequency);
|
||||
}
|
||||
|
||||
private void addGreenScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.green(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addBlueScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.blue(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addRedScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.red(getColor(i, y)));
|
||||
}
|
||||
|
||||
private int getColor(int colorScanSample, int y) {
|
||||
int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
|
||||
return mBitmap.getPixel(x, y);
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/om/sstvencoder/Modes/Scottie1.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_scottie1)
|
||||
class Scottie1 extends Scottie {
|
||||
Scottie1(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 60;
|
||||
mColorScanDurationMs = 138.24;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/om/sstvencoder/Modes/Scottie2.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_scottie2)
|
||||
class Scottie2 extends Scottie {
|
||||
Scottie2(Bitmap bitmap, IOutput output){
|
||||
super(bitmap, output);
|
||||
mVISCode = 56;
|
||||
mColorScanDurationMs = 88.064;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/om/sstvencoder/Modes/ScottieDX.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_scottie_dx)
|
||||
class ScottieDX extends Scottie {
|
||||
ScottieDX(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
mVISCode = 76;
|
||||
mColorScanDurationMs = 345.6;
|
||||
mColorScanSamples = convertMsToSamples(mColorScanDurationMs);
|
||||
}
|
||||
}
|
||||
91
app/src/main/java/om/sstvencoder/Modes/Wraase.java
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Modes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
|
||||
import om.sstvencoder.ModeInterfaces.ModeSize;
|
||||
import om.sstvencoder.Output.IOutput;
|
||||
import om.sstvencoder.R;
|
||||
|
||||
@ModeSize(width = 320, height = 256)
|
||||
@ModeDescription(name = R.string.action_wraaseSC2_180)
|
||||
class Wraase extends Mode {
|
||||
private final int mSyncPulseSamples;
|
||||
private final double mSyncPulseFrequency;
|
||||
|
||||
private final int mPorchSamples;
|
||||
private final double mPorchFrequency;
|
||||
|
||||
private final int mColorScanSamples;
|
||||
|
||||
Wraase(Bitmap bitmap, IOutput output) {
|
||||
super(bitmap, output);
|
||||
|
||||
mVISCode = 55;
|
||||
mColorScanSamples = convertMsToSamples(235.0);
|
||||
|
||||
mSyncPulseSamples = convertMsToSamples(5.5225);
|
||||
mSyncPulseFrequency = 1200.0;
|
||||
|
||||
mPorchSamples = convertMsToSamples(0.5);
|
||||
mPorchFrequency = 1500.0;
|
||||
}
|
||||
|
||||
protected int getTransmissionSamples() {
|
||||
int lineSamples = mSyncPulseSamples + mPorchSamples + 3 * mColorScanSamples;
|
||||
return mBitmap.getHeight() * lineSamples;
|
||||
}
|
||||
|
||||
protected void writeEncodedLine() {
|
||||
addSyncPulse();
|
||||
addPorch();
|
||||
addRedScan(mLine);
|
||||
addGreenScan(mLine);
|
||||
addBlueScan(mLine);
|
||||
}
|
||||
|
||||
private void addSyncPulse() {
|
||||
for (int i = 0; i < mSyncPulseSamples; ++i)
|
||||
setTone(mSyncPulseFrequency);
|
||||
}
|
||||
|
||||
private void addPorch() {
|
||||
for (int i = 0; i < mPorchSamples; ++i)
|
||||
setTone(mPorchFrequency);
|
||||
}
|
||||
|
||||
private void addRedScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.red(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addGreenScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.green(getColor(i, y)));
|
||||
}
|
||||
|
||||
private void addBlueScan(int y) {
|
||||
for (int i = 0; i < mColorScanSamples; ++i)
|
||||
setColorTone(Color.blue(getColor(i, y)));
|
||||
}
|
||||
|
||||
private int getColor(int colorScanSample, int y) {
|
||||
int x = colorScanSample * mBitmap.getWidth() / mColorScanSamples;
|
||||
return mBitmap.getPixel(x, y);
|
||||
}
|
||||
}
|
||||
79
app/src/main/java/om/sstvencoder/Output/AudioOutput.java
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Output;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
|
||||
class AudioOutput implements IOutput {
|
||||
private final double mSampleRate;
|
||||
private short[] mAudioBuffer;
|
||||
private AudioTrack mAudioTrack;
|
||||
private int mBufferPos;
|
||||
|
||||
AudioOutput(double sampleRate) {
|
||||
mSampleRate = sampleRate;
|
||||
mBufferPos = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(int samples) {
|
||||
mAudioBuffer = new short[(5 * (int) mSampleRate) / 2]; // 2.5 seconds of buffer
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
(int) mSampleRate, AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, mAudioBuffer.length * 2,
|
||||
AudioTrack.MODE_STREAM);
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleRate() {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(double value) {
|
||||
if (mBufferPos == mAudioBuffer.length) {
|
||||
mAudioTrack.write(mAudioBuffer, 0, mAudioBuffer.length);
|
||||
mBufferPos = 0;
|
||||
}
|
||||
|
||||
mAudioBuffer[mBufferPos++] = (short) (value * Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(boolean cancel) {
|
||||
if (mAudioTrack != null) {
|
||||
if (!cancel)
|
||||
drainBuffer();
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
mAudioBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void drainBuffer() {
|
||||
// The second run makes sure that the previous buffer indeed got played
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
while (mBufferPos < mAudioBuffer.length)
|
||||
mAudioBuffer[mBufferPos++] = 0;
|
||||
mAudioTrack.write(mAudioBuffer, 0, mAudioBuffer.length);
|
||||
mBufferPos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/src/main/java/om/sstvencoder/Output/IOutput.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Output;
|
||||
|
||||
public interface IOutput {
|
||||
double getSampleRate();
|
||||
|
||||
void init(int samples);
|
||||
|
||||
void write(double value);
|
||||
|
||||
void finish(boolean cancel);
|
||||
}
|
||||
34
app/src/main/java/om/sstvencoder/Output/OutputFactory.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Output;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class OutputFactory {
|
||||
|
||||
public static IOutput createOutputForSending() {
|
||||
double sampleRate = 44100.0;
|
||||
return new AudioOutput(sampleRate);
|
||||
}
|
||||
|
||||
public static IOutput createOutputForSavingAsWave(File filePath) {
|
||||
double sampleRate = 44100.0;
|
||||
|
||||
if (filePath == null)
|
||||
return null;
|
||||
return new WaveFileOutput(filePath, sampleRate);
|
||||
}
|
||||
}
|
||||
131
app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.Output;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
class WaveFileOutput implements IOutput {
|
||||
private final double mSampleRate;
|
||||
private File mFile;
|
||||
private BufferedOutputStream mOutputStream;
|
||||
private int mSamples, mWrittenSamples;
|
||||
|
||||
WaveFileOutput(File file, double sampleRate) {
|
||||
mFile = file;
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public void init(int samples) {
|
||||
int offset = (int) ((0.01 * mSampleRate) / 2.0);
|
||||
mSamples = samples + 2 * offset;
|
||||
mWrittenSamples = 0;
|
||||
InitOutputStream();
|
||||
writeHeader();
|
||||
padWithZeros(offset);
|
||||
}
|
||||
|
||||
private void writeHeader() {
|
||||
try {
|
||||
int numChannels = 1; // mono
|
||||
int bitsPerSample = Short.SIZE;
|
||||
int blockAlign = numChannels * bitsPerSample / Byte.SIZE;
|
||||
int subchunk2Size = mSamples * blockAlign;
|
||||
|
||||
mOutputStream.write("RIFF".getBytes()); // ChunkID
|
||||
mOutputStream.write(toLittleEndian(36 + subchunk2Size)); // ChunkSize
|
||||
mOutputStream.write("WAVE".getBytes()); // Format
|
||||
|
||||
mOutputStream.write("fmt ".getBytes()); // Subchunk1ID
|
||||
mOutputStream.write(toLittleEndian(16)); // Subchunk1Size
|
||||
mOutputStream.write(toLittleEndian((short) 1)); // AudioFormat
|
||||
mOutputStream.write(toLittleEndian((short) numChannels)); // NumChannels
|
||||
mOutputStream.write(toLittleEndian((int) mSampleRate)); // SampleRate
|
||||
mOutputStream.write(toLittleEndian((int) mSampleRate * blockAlign)); // ByteRate
|
||||
mOutputStream.write(toLittleEndian((short) blockAlign)); // BlockAlign
|
||||
mOutputStream.write(toLittleEndian((short) bitsPerSample)); // BitsPerSample
|
||||
|
||||
mOutputStream.write("data".getBytes()); // Subchunk2ID
|
||||
mOutputStream.write(toLittleEndian(subchunk2Size)); // Subchunk2Size
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOutputStream() {
|
||||
try {
|
||||
mOutputStream = new BufferedOutputStream(new FileOutputStream(mFile));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleRate() {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(double value) {
|
||||
short tmp = (short) (value * Short.MAX_VALUE);
|
||||
++mWrittenSamples;
|
||||
try {
|
||||
mOutputStream.write(toLittleEndian(tmp));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(boolean cancel) {
|
||||
if (!cancel)
|
||||
padWithZeros(mSamples);
|
||||
|
||||
try {
|
||||
mOutputStream.close();
|
||||
mOutputStream = null;
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
if (mFile != null) {
|
||||
if (cancel)
|
||||
mFile.delete();
|
||||
mFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void padWithZeros(int count) {
|
||||
try {
|
||||
while (mWrittenSamples++ < count)
|
||||
mOutputStream.write(toLittleEndian((short) 0));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] toLittleEndian(int value) {
|
||||
byte[] buffer = new byte[4];
|
||||
buffer[0] = (byte) (value & 255);
|
||||
buffer[1] = (byte) ((value >> 8) & 255);
|
||||
buffer[2] = (byte) ((value >> 16) & 255);
|
||||
buffer[3] = (byte) ((value >> 24) & 255);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private byte[] toLittleEndian(short value) {
|
||||
byte[] buffer = new byte[2];
|
||||
buffer[0] = (byte) (value & 255);
|
||||
buffer[1] = (byte) ((value >> 8) & 255);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
147
app/src/main/java/om/sstvencoder/Settings.java
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonToken;
|
||||
import android.util.JsonWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
class Settings {
|
||||
private final static String IMAGE_URI = "image_uri";
|
||||
private final static String TEXT_OVERLAY_PATH = "text_overlay_path";
|
||||
private final String mFileName;
|
||||
private Context mContext;
|
||||
private String mImageUri;
|
||||
private String mTextOverlayPath;
|
||||
|
||||
private Settings() {
|
||||
mFileName = "settings.json";
|
||||
}
|
||||
|
||||
Settings(Context context) {
|
||||
this();
|
||||
mContext = context;
|
||||
mImageUri = "";
|
||||
}
|
||||
|
||||
boolean load() {
|
||||
boolean loaded = false;
|
||||
JsonReader reader = null;
|
||||
try {
|
||||
InputStream in = new FileInputStream(getFile());
|
||||
reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
|
||||
readImageUri(reader);
|
||||
readTextOverlayPath(reader);
|
||||
loaded = true;
|
||||
} catch (Exception ignore) {
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
boolean save() {
|
||||
boolean saved = false;
|
||||
JsonWriter writer = null;
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(getFile());
|
||||
writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
writer.setIndent(" ");
|
||||
writeImageUri(writer);
|
||||
writeTextOverlayPath(writer);
|
||||
saved = true;
|
||||
} catch (Exception ignore) {
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
void setImageUri(Uri uri) {
|
||||
mImageUri = uri == null ? "" : uri.toString();
|
||||
}
|
||||
|
||||
Uri getImageUri() {
|
||||
if ("".equals(mImageUri))
|
||||
return null;
|
||||
return Uri.parse(mImageUri);
|
||||
}
|
||||
|
||||
File getTextOverlayFile() {
|
||||
if (mTextOverlayPath == null)
|
||||
mTextOverlayPath = new File(mContext.getFilesDir(), "text_overlay.json").getPath();
|
||||
return new File(mTextOverlayPath);
|
||||
}
|
||||
|
||||
private File getFile() {
|
||||
return new File(mContext.getFilesDir(), mFileName);
|
||||
}
|
||||
|
||||
private void writeImageUri(JsonWriter writer) throws IOException {
|
||||
writer.beginObject();
|
||||
writer.name(IMAGE_URI).value(mImageUri);
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
private void writeTextOverlayPath(JsonWriter writer) throws IOException {
|
||||
writer.beginObject();
|
||||
writer.name(TEXT_OVERLAY_PATH).value(mTextOverlayPath);
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
private void readImageUri(JsonReader reader) throws IOException {
|
||||
reader.beginObject();
|
||||
{
|
||||
reader.nextName();
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull();
|
||||
mImageUri = null;
|
||||
} else
|
||||
mImageUri = reader.nextString();
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
|
||||
private void readTextOverlayPath(JsonReader reader) throws IOException {
|
||||
reader.beginObject();
|
||||
{
|
||||
reader.nextName();
|
||||
mTextOverlayPath = reader.nextString();
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
}
|
||||
40
app/src/main/java/om/sstvencoder/TextOverlay/IReader.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IReader {
|
||||
void beginRootObject() throws IOException;
|
||||
|
||||
void beginObject() throws IOException;
|
||||
|
||||
void endObject() throws IOException;
|
||||
|
||||
void beginArray() throws IOException;
|
||||
|
||||
void endArray() throws IOException;
|
||||
|
||||
boolean hasNext() throws IOException;
|
||||
|
||||
String readString() throws IOException;
|
||||
|
||||
boolean readBoolean() throws IOException;
|
||||
|
||||
float readFloat() throws IOException;
|
||||
|
||||
int readInt() throws IOException;
|
||||
}
|
||||
40
app/src/main/java/om/sstvencoder/TextOverlay/IWriter.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IWriter {
|
||||
void beginRootObject() throws IOException;
|
||||
|
||||
void beginObject(@NonNull String name) throws IOException;
|
||||
|
||||
void endObject() throws IOException;
|
||||
|
||||
void beginArray(@NonNull String name) throws IOException;
|
||||
|
||||
void endArray() throws IOException;
|
||||
|
||||
void write(@NonNull String name, String value) throws IOException;
|
||||
|
||||
void write(@NonNull String name, boolean value) throws IOException;
|
||||
|
||||
void write(@NonNull String name, float value) throws IOException;
|
||||
|
||||
void write(@NonNull String name, int value) throws IOException;
|
||||
}
|
||||
96
app/src/main/java/om/sstvencoder/TextOverlay/Label.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Label implements Serializable {
|
||||
private String mText;
|
||||
private float mTextSize;
|
||||
private String mFamilyName;
|
||||
private boolean mBold, mItalic;
|
||||
private int mForeColor, mBackColor;
|
||||
|
||||
public Label() {
|
||||
mText = "";
|
||||
mTextSize = 2.0f;
|
||||
mFamilyName = null;
|
||||
mBold = true;
|
||||
mItalic = false;
|
||||
mForeColor = Color.BLACK;
|
||||
mBackColor = Color.TRANSPARENT;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
if (text != null)
|
||||
mText = text;
|
||||
}
|
||||
|
||||
public float getTextSize() {
|
||||
return mTextSize;
|
||||
}
|
||||
|
||||
public void setTextSize(float textSize) {
|
||||
if (textSize > 0f)
|
||||
mTextSize = textSize;
|
||||
}
|
||||
|
||||
public String getFamilyName() {
|
||||
return mFamilyName;
|
||||
}
|
||||
|
||||
public void setFamilyName(String familyName) {
|
||||
mFamilyName = familyName;
|
||||
}
|
||||
|
||||
public boolean getBold() {
|
||||
return mBold;
|
||||
}
|
||||
|
||||
public void setBold(boolean bold) {
|
||||
mBold = bold;
|
||||
}
|
||||
|
||||
public boolean getItalic() {
|
||||
return mItalic;
|
||||
}
|
||||
|
||||
public void setItalic(boolean italic) {
|
||||
mItalic = italic;
|
||||
}
|
||||
|
||||
public int getForeColor() {
|
||||
return mForeColor;
|
||||
}
|
||||
|
||||
public void setForeColor(int color) {
|
||||
mForeColor = color;
|
||||
}
|
||||
|
||||
public int getBackColor() {
|
||||
return mBackColor;
|
||||
}
|
||||
|
||||
public void setBackColor(int color) {
|
||||
mBackColor = color;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import om.sstvencoder.Utility;
|
||||
|
||||
public class LabelCollection {
|
||||
private class Size {
|
||||
private float mW, mH;
|
||||
|
||||
Size(float w, float h) {
|
||||
mW = w;
|
||||
mH = h;
|
||||
}
|
||||
|
||||
float width() {
|
||||
return mW;
|
||||
}
|
||||
|
||||
float height() {
|
||||
return mH;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<LabelContainer> mLabels;
|
||||
private Size mScreenSize;
|
||||
private float mTextSizeFactor;
|
||||
private LabelContainer mActiveLabel, mEditLabel;
|
||||
private float mPreviousX, mPreviousY;
|
||||
|
||||
public LabelCollection() {
|
||||
mLabels = new LinkedList<>();
|
||||
mPreviousX = 0f;
|
||||
mPreviousY = 0f;
|
||||
}
|
||||
|
||||
public void update(float w, float h) {
|
||||
if (mScreenSize != null) {
|
||||
float x = (w - mScreenSize.width()) / 2f;
|
||||
float y = (h - mScreenSize.height()) / 2f;
|
||||
for (LabelContainer label : mLabels)
|
||||
label.offset(x, y);
|
||||
}
|
||||
mScreenSize = new Size(w, h);
|
||||
mTextSizeFactor = getTextSizeFactor(w, h);
|
||||
for (LabelContainer label : mLabels)
|
||||
label.update(mTextSizeFactor, w, h);
|
||||
}
|
||||
|
||||
private float getTextSizeFactor(float w, float h) {
|
||||
Rect bounds = Utility.getEmbeddedRect((int) w, (int) h, 320, 240);
|
||||
return 0.1f * bounds.height();
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
for (LabelContainer label : mLabels)
|
||||
label.draw(canvas);
|
||||
if (mActiveLabel != null)
|
||||
mActiveLabel.drawActive(canvas);
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas, Rect src, Rect dst) {
|
||||
for (LabelContainer label : mLabels)
|
||||
label.draw(canvas, src, dst);
|
||||
}
|
||||
|
||||
public boolean moveLabelBegin(float x, float y) {
|
||||
mActiveLabel = find(x, y);
|
||||
if (mActiveLabel == null)
|
||||
return false;
|
||||
mLabels.remove(mActiveLabel);
|
||||
mPreviousX = x;
|
||||
mPreviousY = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void moveLabel(float x, float y) {
|
||||
mActiveLabel.offset(x - mPreviousX, y - mPreviousY);
|
||||
mActiveLabel.update(mTextSizeFactor, mScreenSize.width(), mScreenSize.height());
|
||||
mPreviousX = x;
|
||||
mPreviousY = y;
|
||||
}
|
||||
|
||||
public void moveLabelEnd() {
|
||||
mLabels.add(mActiveLabel);
|
||||
mActiveLabel = null;
|
||||
mPreviousX = 0f;
|
||||
mPreviousY = 0f;
|
||||
}
|
||||
|
||||
public Label editLabelBegin(float x, float y) {
|
||||
mEditLabel = find(x, y);
|
||||
if (mEditLabel == null) {
|
||||
mEditLabel = new LabelContainer(new Label());
|
||||
mEditLabel.offset(x, y);
|
||||
}
|
||||
return mEditLabel.getContent();
|
||||
}
|
||||
|
||||
public void editLabelEnd(Label label) {
|
||||
if (label != null) { // not canceled
|
||||
if ("".equals(label.getText().trim())) {
|
||||
if (mLabels.contains(mEditLabel))
|
||||
mLabels.remove(mEditLabel);
|
||||
} else {
|
||||
if (!mLabels.contains(mEditLabel))
|
||||
mLabels.add(mEditLabel);
|
||||
mEditLabel.setContent(label);
|
||||
mEditLabel.update(mTextSizeFactor, mScreenSize.width(), mScreenSize.height());
|
||||
}
|
||||
}
|
||||
mEditLabel = null;
|
||||
}
|
||||
|
||||
private LabelContainer find(float x, float y) {
|
||||
for (LabelContainer label : mLabels) {
|
||||
if (label.contains(x, y))
|
||||
return label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void add(LabelContainer label) {
|
||||
if (mLabels.size() == 0)
|
||||
mLabels.add(label);
|
||||
else
|
||||
mLabels.add(0, label);
|
||||
}
|
||||
|
||||
public void write(@NonNull IWriter writer) throws IOException {
|
||||
writer.beginRootObject();
|
||||
{
|
||||
writer.write("width", mScreenSize.width());
|
||||
writer.write("height", mScreenSize.height());
|
||||
writer.beginArray("labels");
|
||||
{
|
||||
for (LabelContainer label : mLabels)
|
||||
label.write(writer);
|
||||
}
|
||||
writer.endArray();
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
public void read(@NonNull IReader reader) throws IOException {
|
||||
reader.beginRootObject();
|
||||
{
|
||||
float w = reader.readFloat();
|
||||
float h = reader.readFloat();
|
||||
reader.beginArray();
|
||||
{
|
||||
while (reader.hasNext()) {
|
||||
LabelContainer label = new LabelContainer(new Label());
|
||||
label.read(reader);
|
||||
add(label);
|
||||
}
|
||||
}
|
||||
reader.endArray();
|
||||
update(w, h);
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
}
|
||||
117
app/src/main/java/om/sstvencoder/TextOverlay/LabelContainer.java
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class LabelContainer {
|
||||
private Label mLabel;
|
||||
private LabelPainter mPainter;
|
||||
private float mX, mY; // left-bottom corner
|
||||
|
||||
LabelContainer(@NonNull Label label) {
|
||||
mLabel = label;
|
||||
mPainter = new LabelPainter(label);
|
||||
mX = mY = 0f;
|
||||
}
|
||||
|
||||
boolean contains(float x, float y) {
|
||||
return mPainter.getBounds().contains(x, y);
|
||||
}
|
||||
|
||||
void draw(Canvas canvas) {
|
||||
mPainter.draw(canvas);
|
||||
}
|
||||
|
||||
void drawActive(Canvas canvas) {
|
||||
mPainter.drawActive(canvas);
|
||||
}
|
||||
|
||||
void draw(Canvas canvas, Rect src, Rect dst) {
|
||||
mPainter.draw(canvas, src, dst);
|
||||
}
|
||||
|
||||
void offset(float x, float y) {
|
||||
mX += x;
|
||||
mY += y;
|
||||
}
|
||||
|
||||
void update(float textSizeFactor, float screenW, float screenH) {
|
||||
mPainter.update(textSizeFactor, screenW, screenH, mX, mY);
|
||||
}
|
||||
|
||||
Label getContent() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
void setContent(@NonNull Label label) {
|
||||
mLabel = label;
|
||||
mPainter.setLabel(label);
|
||||
}
|
||||
|
||||
void write(IWriter writer) throws IOException {
|
||||
writer.beginRootObject();
|
||||
{
|
||||
writer.write("position_x", mX);
|
||||
writer.write("position_y", mY);
|
||||
writer.beginObject("label");
|
||||
{
|
||||
writeLabel(writer, mLabel);
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
void read(IReader reader) throws IOException {
|
||||
reader.beginRootObject();
|
||||
{
|
||||
mX = reader.readFloat();
|
||||
mY = reader.readFloat();
|
||||
reader.beginObject();
|
||||
{
|
||||
readLabel(reader, mLabel);
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
|
||||
private void writeLabel(IWriter writer, Label label) throws IOException {
|
||||
writer.write("text", label.getText());
|
||||
writer.write("text_size", label.getTextSize());
|
||||
writer.write("family_name", label.getFamilyName());
|
||||
writer.write("bold", label.getBold());
|
||||
writer.write("italic", label.getItalic());
|
||||
writer.write("fore_color", label.getForeColor());
|
||||
writer.write("back_color", label.getBackColor());
|
||||
}
|
||||
|
||||
private void readLabel(IReader reader, Label label) throws IOException {
|
||||
label.setText(reader.readString());
|
||||
label.setTextSize(reader.readFloat());
|
||||
label.setFamilyName(reader.readString());
|
||||
label.setBold(reader.readBoolean());
|
||||
label.setItalic(reader.readBoolean());
|
||||
label.setForeColor(reader.readInt());
|
||||
label.setBackColor(reader.readInt());
|
||||
}
|
||||
}
|
||||
|
||||
303
app/src/main/java/om/sstvencoder/TextOverlay/LabelPainter.java
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder.TextOverlay;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
class LabelPainter {
|
||||
private interface IDrawer {
|
||||
void draw(Canvas canvas);
|
||||
|
||||
void drawShadow(Canvas canvas);
|
||||
|
||||
void draw(Canvas canvas, Rect src, Rect dst);
|
||||
|
||||
RectF getBounds();
|
||||
}
|
||||
|
||||
private class InDrawer implements IDrawer {
|
||||
private float mSizeFactor;
|
||||
private float mX, mY;
|
||||
|
||||
private InDrawer(float sizeFactor, float x, float y) {
|
||||
mSizeFactor = sizeFactor;
|
||||
mX = x;
|
||||
mY = y;
|
||||
setPaintSettings(mSizeFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawText(mLabel.getText(), mX, mY, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawShadow(Canvas canvas) {
|
||||
RectF bounds = new RectF(getBounds());
|
||||
float rx = 10f;
|
||||
float ry = 10f;
|
||||
|
||||
mPaint.setColor(Color.LTGRAY);
|
||||
mPaint.setAlpha(100);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawRoundRect(bounds, rx, ry, mPaint);
|
||||
|
||||
mPaint.setAlpha(255);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mPaint.setColor(Color.RED);
|
||||
bounds.inset(-5.0f, -5.0f);
|
||||
canvas.drawRoundRect(bounds, rx, ry, mPaint);
|
||||
|
||||
mPaint.setColor(Color.GREEN);
|
||||
bounds.inset(1.0f, 1.0f);
|
||||
canvas.drawRoundRect(bounds, rx, ry, mPaint);
|
||||
|
||||
mPaint.setColor(Color.BLUE);
|
||||
bounds.inset(1.0f, 1.0f);
|
||||
canvas.drawRoundRect(bounds, rx, ry, mPaint);
|
||||
|
||||
setPaintSettings(mSizeFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Rect src, Rect dst) {
|
||||
float factor = (dst.height() / (float) src.height());
|
||||
float x = (mX - src.left) * factor;
|
||||
float y = (mY - src.top) * factor;
|
||||
setTextSize(factor * mSizeFactor);
|
||||
canvas.drawText(mLabel.getText(), x, y, mPaint);
|
||||
setTextSize(mSizeFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RectF getBounds() {
|
||||
RectF bounds = new RectF(getTextBounds());
|
||||
bounds.offset(mX, mY);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private Rect getTextBounds() {
|
||||
Rect bounds = new Rect();
|
||||
String text = mLabel.getText();
|
||||
mPaint.getTextBounds(text, 0, text.length(), bounds);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private void setPaintSettings(float sizeFactor) {
|
||||
mPaint.setAlpha(255);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setColor(mLabel.getForeColor());
|
||||
mPaint.setTypeface(Typeface.create(mLabel.getFamilyName(), getTypeface()));
|
||||
setTextSize(sizeFactor);
|
||||
}
|
||||
|
||||
private void setTextSize(float sizeFactor) {
|
||||
mPaint.setTextSize(mLabel.getTextSize() * sizeFactor);
|
||||
}
|
||||
|
||||
private int getTypeface() {
|
||||
int typeface = Typeface.NORMAL;
|
||||
|
||||
if (mLabel.getBold() && mLabel.getItalic())
|
||||
typeface = Typeface.BOLD_ITALIC;
|
||||
else {
|
||||
if (mLabel.getBold())
|
||||
typeface = Typeface.BOLD;
|
||||
else if (mLabel.getItalic())
|
||||
typeface = Typeface.ITALIC;
|
||||
}
|
||||
return typeface;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutDrawer implements IDrawer {
|
||||
private Path mPath;
|
||||
private RectF mBoundsOutside;
|
||||
private float mMinSize, mX, mY;
|
||||
|
||||
private OutDrawer(float min) {
|
||||
mMinSize = min * 0.5f;
|
||||
mPaint.setAlpha(255);
|
||||
}
|
||||
|
||||
private void leftOut(RectF rect, float screenH) {
|
||||
mX = 0f;
|
||||
mY = Math.min(Math.max(mMinSize, rect.top + rect.height() * 0.5f), screenH - mMinSize);
|
||||
mPath = getLeftAlignedTriangle(mX, mY, mMinSize);
|
||||
mBoundsOutside = new RectF(mX, mY - mMinSize, mX + mMinSize, mY + mMinSize);
|
||||
}
|
||||
|
||||
private void topOut(RectF rect, float screenW) {
|
||||
mX = Math.min(Math.max(mMinSize, rect.left + rect.width() * 0.5f), screenW - mMinSize);
|
||||
mY = 0f;
|
||||
mPath = getTopAlignedTriangle(mX, mY, mMinSize);
|
||||
mBoundsOutside = new RectF(mX - mMinSize, mY, mX + mMinSize * 0.5f, mY + mMinSize);
|
||||
}
|
||||
|
||||
private void rightOut(RectF rect, float screenW, float screenH) {
|
||||
mX = screenW;
|
||||
mY = Math.min(Math.max(mMinSize, rect.top + rect.height() * 0.5f), screenH - mMinSize);
|
||||
mPath = getRightAlignedTriangle(mX, mY, mMinSize);
|
||||
mBoundsOutside = new RectF(mX - mMinSize, mY - mMinSize, mX, mY + mMinSize);
|
||||
}
|
||||
|
||||
private void bottomOut(RectF rect, float screenW, float screenH) {
|
||||
mX = Math.min(Math.max(mMinSize, rect.left + rect.width() * 0.5f), screenW - mMinSize);
|
||||
mY = screenH;
|
||||
mPath = getBottomAlignedTriangle(mX, mY, mMinSize);
|
||||
mBoundsOutside = new RectF(mX - mMinSize, mY - mMinSize, mX + mMinSize, mY);
|
||||
}
|
||||
|
||||
private Path getLeftAlignedTriangle(float x, float y, float r) {
|
||||
Path path = new Path();
|
||||
path.moveTo(x, y - r);
|
||||
path.lineTo(x, y + r);
|
||||
path.lineTo(x + r * 0.6f, y);
|
||||
path.lineTo(x, y - r);
|
||||
return path;
|
||||
}
|
||||
|
||||
private Path getTopAlignedTriangle(float x, float y, float r) {
|
||||
Path path = new Path();
|
||||
path.moveTo(x - r, y);
|
||||
path.lineTo(x, y + r * 0.6f);
|
||||
path.lineTo(x + r, y);
|
||||
path.lineTo(x - r, y);
|
||||
return path;
|
||||
}
|
||||
|
||||
private Path getRightAlignedTriangle(float x, float y, float r) {
|
||||
Path path = new Path();
|
||||
path.moveTo(x, y - r);
|
||||
path.lineTo(x - r * 0.6f, y);
|
||||
path.lineTo(x, y + r);
|
||||
path.lineTo(x, y - r);
|
||||
return path;
|
||||
}
|
||||
|
||||
private Path getBottomAlignedTriangle(float x, float y, float r) {
|
||||
Path path = new Path();
|
||||
path.moveTo(x - r, y);
|
||||
path.lineTo(x, y - r * 0.6f);
|
||||
path.lineTo(x + r, y);
|
||||
path.lineTo(x - r, y);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mPaint.setColor(mLabel.getForeColor());
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
|
||||
mPaint.setColor(Color.WHITE);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Rect src, Rect dst) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawShadow(Canvas canvas) {
|
||||
float r = 2f * mMinSize;
|
||||
|
||||
mPaint.setColor(Color.LTGRAY);
|
||||
mPaint.setAlpha(100);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawCircle(mX, mY, r, mPaint);
|
||||
|
||||
mPaint.setAlpha(255);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mPaint.setColor(Color.RED);
|
||||
canvas.drawCircle(mX, mY, r + 1f, mPaint);
|
||||
mPaint.setColor(Color.GREEN);
|
||||
canvas.drawCircle(mX, mY, r, mPaint);
|
||||
mPaint.setColor(Color.BLUE);
|
||||
canvas.drawCircle(mX, mY, r - 1f, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RectF getBounds() {
|
||||
return mBoundsOutside;
|
||||
}
|
||||
}
|
||||
|
||||
private final Paint mPaint;
|
||||
private Label mLabel;
|
||||
private IDrawer mDrawer;
|
||||
|
||||
LabelPainter(@NonNull Label label) {
|
||||
mLabel = label;
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
void draw(Canvas canvas) {
|
||||
mDrawer.draw(canvas);
|
||||
}
|
||||
|
||||
void drawActive(Canvas canvas) {
|
||||
mDrawer.drawShadow(canvas);
|
||||
mDrawer.draw(canvas);
|
||||
}
|
||||
|
||||
void draw(Canvas canvas, Rect src, Rect dst) {
|
||||
mDrawer.draw(canvas, src, dst);
|
||||
}
|
||||
|
||||
RectF getBounds() {
|
||||
return mDrawer.getBounds();
|
||||
}
|
||||
|
||||
void setLabel(@NonNull Label label) {
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
void update(float sizeFactor, float screenW, float screenH, float x, float y) {
|
||||
InDrawer inDrawer = new InDrawer(sizeFactor, x, y);
|
||||
|
||||
RectF rect = inDrawer.getBounds();
|
||||
float minSize = 1.5f * sizeFactor;
|
||||
|
||||
OutDrawer outDrawer = null;
|
||||
if (rect.right < minSize) { // left out
|
||||
outDrawer = new OutDrawer(minSize);
|
||||
outDrawer.leftOut(rect, screenH);
|
||||
} else if (rect.bottom < minSize) {// top out
|
||||
outDrawer = new OutDrawer(minSize);
|
||||
outDrawer.topOut(rect, screenW);
|
||||
} else if (rect.left > (screenW - minSize)) { // right out
|
||||
outDrawer = new OutDrawer(minSize);
|
||||
outDrawer.rightOut(rect, screenW, screenH);
|
||||
} else if (rect.top > (screenH - minSize)) { // bottom out
|
||||
outDrawer = new OutDrawer(minSize);
|
||||
outDrawer.bottomOut(rect, screenW, screenH);
|
||||
}
|
||||
|
||||
mDrawer = outDrawer == null ? inDrawer : outDrawer;
|
||||
}
|
||||
}
|
||||
200
app/src/main/java/om/sstvencoder/TextOverlayTemplate.java
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonToken;
|
||||
import android.util.JsonWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import om.sstvencoder.TextOverlay.IReader;
|
||||
import om.sstvencoder.TextOverlay.IWriter;
|
||||
import om.sstvencoder.TextOverlay.LabelCollection;
|
||||
|
||||
class TextOverlayTemplate {
|
||||
private class LabelCollectionWriter implements IWriter {
|
||||
private JsonWriter mWriter;
|
||||
|
||||
private LabelCollectionWriter(@NonNull JsonWriter writer) {
|
||||
mWriter = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginRootObject() throws IOException {
|
||||
mWriter.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginObject(@NonNull String name) throws IOException {
|
||||
mWriter.name(name);
|
||||
mWriter.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endObject() throws IOException {
|
||||
mWriter.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginArray(@NonNull String name) throws IOException {
|
||||
mWriter.name(name);
|
||||
mWriter.beginArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endArray() throws IOException {
|
||||
mWriter.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull String name, String value) throws IOException {
|
||||
mWriter.name(name).value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull String name, boolean value) throws IOException {
|
||||
mWriter.name(name).value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull String name, float value) throws IOException {
|
||||
mWriter.name(name).value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull String name, int value) throws IOException {
|
||||
mWriter.name(name).value(value);
|
||||
}
|
||||
}
|
||||
|
||||
private class LabelCollectionReader implements IReader {
|
||||
private JsonReader mReader;
|
||||
|
||||
private LabelCollectionReader(@NonNull JsonReader reader) {
|
||||
mReader = reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginRootObject() throws IOException {
|
||||
mReader.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginObject() throws IOException {
|
||||
mReader.nextName();
|
||||
mReader.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endObject() throws IOException {
|
||||
mReader.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginArray() throws IOException {
|
||||
mReader.nextName();
|
||||
mReader.beginArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endArray() throws IOException {
|
||||
mReader.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() throws IOException {
|
||||
return mReader.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString() throws IOException {
|
||||
mReader.nextName();
|
||||
if (mReader.peek() == JsonToken.NULL) {
|
||||
mReader.nextNull();
|
||||
return null;
|
||||
}
|
||||
return mReader.nextString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() throws IOException {
|
||||
mReader.nextName();
|
||||
return mReader.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() throws IOException {
|
||||
mReader.nextName();
|
||||
return Float.valueOf(mReader.nextString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
mReader.nextName();
|
||||
return mReader.nextInt();
|
||||
}
|
||||
}
|
||||
|
||||
boolean load(@NonNull LabelCollection labels, File file) {
|
||||
boolean loaded = false;
|
||||
JsonReader jsonReader = null;
|
||||
try {
|
||||
InputStream in = new FileInputStream(file);
|
||||
jsonReader = new JsonReader(new InputStreamReader(in, "UTF-8"));
|
||||
labels.read(new LabelCollectionReader(jsonReader));
|
||||
loaded = true;
|
||||
} catch (Exception ignore) {
|
||||
} finally {
|
||||
if (jsonReader != null) {
|
||||
try {
|
||||
jsonReader.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
boolean save(@NonNull LabelCollection labels, File file) {
|
||||
boolean saved = false;
|
||||
JsonWriter jsonWriter = null;
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(file);
|
||||
jsonWriter = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
jsonWriter.setIndent(" ");
|
||||
labels.write(new LabelCollectionWriter(jsonWriter));
|
||||
saved = true;
|
||||
} catch (Exception ignore) {
|
||||
} finally {
|
||||
if (jsonWriter != null) {
|
||||
try {
|
||||
jsonWriter.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
}
|
||||
113
app/src/main/java/om/sstvencoder/Utility.java
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2017 Olga Miller <olga.rgb@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package om.sstvencoder;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.media.ExifInterface;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class Utility {
|
||||
@NonNull
|
||||
public static Rect getEmbeddedRect(int w, int h, int iw, int ih) {
|
||||
Rect rect;
|
||||
|
||||
int ow = (9 * w) / 10;
|
||||
int oh = (9 * h) / 10;
|
||||
|
||||
if (iw * oh < ow * ih) {
|
||||
rect = new android.graphics.Rect(0, 0, (iw * oh) / ih, oh);
|
||||
rect.offset((w - (iw * oh) / ih) / 2, (h - oh) / 2);
|
||||
} else {
|
||||
rect = new android.graphics.Rect(0, 0, ow, (ih * ow) / iw);
|
||||
rect.offset((w - ow) / 2, (h - (ih * ow) / iw) / 2);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
static String createMessage(Exception ex) {
|
||||
String message = ex.getMessage() + "\n";
|
||||
for (StackTraceElement el : ex.getStackTrace())
|
||||
message += "\n" + el.toString();
|
||||
return message;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Intent createEmailIntent(final String subject, final String text) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/email");
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"olga.rgb@gmail.com"});
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text);
|
||||
return intent;
|
||||
}
|
||||
|
||||
static int convertToDegrees(int exifOrientation) {
|
||||
switch (exifOrientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static ContentValues getWavContentValues(File file) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Audio.Media.ALBUM, "SSTV Encoder");
|
||||
values.put(MediaStore.Audio.Media.ARTIST, "");
|
||||
values.put(MediaStore.Audio.Media.DATA, file.toString());
|
||||
values.put(MediaStore.Audio.Media.IS_MUSIC, true);
|
||||
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav");
|
||||
values.put(MediaStore.Audio.Media.TITLE, file.getName());
|
||||
return values;
|
||||
}
|
||||
|
||||
static File createImageFilePath() {
|
||||
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
if (!isExternalStorageWritable())
|
||||
return null;
|
||||
return new File(dir, createFileName() + ".jpg");
|
||||
}
|
||||
|
||||
static File createWaveFilePath() {
|
||||
// sdcard/Music
|
||||
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
||||
if (!isExternalStorageWritable())
|
||||
return null;
|
||||
return new File(dir, createFileName() + ".wav");
|
||||
}
|
||||
|
||||
private static String createFileName() {
|
||||
return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
||||
}
|
||||
|
||||
private static boolean isExternalStorageWritable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
}
|
||||
53
app/src/main/res/layout/activity_edit_text.xml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".EditTextActivity">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone|flagNoExtractUi"
|
||||
android:inputType="text"
|
||||
android:textSize="32sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/edit_italic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.3"
|
||||
android:text="@string/italic"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/edit_bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.3"
|
||||
android:checked="true"
|
||||
android:text="@string/bold"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/edit_text_size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.4"
|
||||
android:minHeight="32sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<om.sstvencoder.ColorPalette.ColorPaletteView
|
||||
android:id="@+id/edit_color"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
20
app/src/main/res/layout/activity_main.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<om.sstvencoder.CropView
|
||||
android:id="@+id/cropView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
12
app/src/main/res/menu/menu_edit_text.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".EditTextActivity">
|
||||
<item
|
||||
android:id="@+id/action_done"
|
||||
android:icon="@mipmap/sym_keyboard_done_lxx_dark"
|
||||
android:title="@string/action_done"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
43
app/src/main/res/menu/menu_main.xml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_pick_picture"
|
||||
android:icon="@android:drawable/ic_menu_gallery"
|
||||
android:title="@string/action_pick_picture"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_take_picture"
|
||||
android:icon="@android:drawable/ic_menu_camera"
|
||||
android:title="@string/action_take_picture"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_save_wave"
|
||||
android:icon="@android:drawable/ic_menu_save"
|
||||
android:title="@string/action_save_wave"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_play"
|
||||
android:icon="@android:drawable/ic_media_play"
|
||||
android:title="@string/action_play"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_stop"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:title="@string/action_stop"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_rotate"
|
||||
android:icon="@android:drawable/ic_menu_rotate"
|
||||
android:title="@string/action_rotate"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_modes"
|
||||
android:title="@string/action_modes"
|
||||
app:showAsAction="ifRoom">
|
||||
<menu/>
|
||||
</item>
|
||||
</menu>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
app/src/main/res/mipmap-hdpi/sym_keyboard_done_lxx_dark.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/sym_keyboard_done_lxx_dark.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/sym_keyboard_done_lxx_dark.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/sym_keyboard_done_lxx_dark.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
36
app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">SSTV Encoder</string>
|
||||
<string name="action_pick_picture">Pick Picture</string>
|
||||
<string name="action_take_picture">Take Picture</string>
|
||||
<string name="action_save_wave">Save Wave File</string>
|
||||
<string name="action_stop">Stop</string>
|
||||
<string name="action_play">Play</string>
|
||||
<string name="action_rotate">Rotate Image</string>
|
||||
<string name="action_done">Done</string>
|
||||
<string name="action_modes">Modes</string>
|
||||
<string name="action_martin1">Martin 1</string>
|
||||
<string name="action_martin2">Martin 2</string>
|
||||
<string name="action_pd50">PD 50</string>
|
||||
<string name="action_pd90">PD 90</string>
|
||||
<string name="action_pd120">PD 120</string>
|
||||
<string name="action_pd160">PD 160</string>
|
||||
<string name="action_pd180">PD 180</string>
|
||||
<string name="action_pd240">PD 240</string>
|
||||
<string name="action_pd290">PD 290</string>
|
||||
<string name="action_scottie1">Scottie 1</string>
|
||||
<string name="action_scottie2">Scottie 2</string>
|
||||
<string name="action_scottie_dx">Scottie DX</string>
|
||||
<string name="action_robot36">Robot 36</string>
|
||||
<string name="action_robot72">Robot 72</string>
|
||||
<string name="action_wraaseSC2_180">Wraase SC2 180</string>
|
||||
<string name="load_img_err_title">Image loading error</string>
|
||||
<string name="load_img_orientation_err_title">Image orientation error</string>
|
||||
<string name="load_img_err_txt_unsupported">Unsupported content.</string>
|
||||
<string name="btn_send_email">Send Email</string>
|
||||
<string name="btn_ok">OK</string>
|
||||
<string name="email_subject">SSTV Encoder - Bug Report</string>
|
||||
<string name="chooser_title">Send Bug Report:</string>
|
||||
<string name="bold">Bold</string>
|
||||
<string name="italic">Italic</string>
|
||||
</resources>
|
||||
3
app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<style name="AppTheme" parent="Base.Theme.AppCompat"/>
|
||||
</resources>
|
||||
6
app/src/main/res/xml/paths.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path
|
||||
name="pictures"
|
||||
path=""/>
|
||||
</paths>
|
||||
23
build.gradle
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.2.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
17
gradle.properties
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#Mon Dec 28 10:00:20 PST 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||
160
gradlew
vendored
Executable file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
1
settings.gradle
Normal file
|
|
@ -0,0 +1 @@
|
|||
include ':app'
|
||||