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'
|
||||||