Improve the image editor.

A variety of improvements to the image editor, such as:

- New, fullscreen styling
- Smoother lines
- Better text and sticker handling
- Improved color picker with a history pallette
- New highlighter tool
master
Greyson Parrelli 2018-05-29 15:45:20 -04:00
parent dace93abb3
commit 0999359454
55 changed files with 701 additions and 425 deletions

View File

@ -375,7 +375,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.ScribbleActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:theme="@style/TextSecure.ScribbleTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 B

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 B

After

Width:  |  Height:  |  Size: 840 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/white"/>
</shape>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/circle_white"/>
<ImageView
android:id="@+id/palette_item_foreground"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/circle_white"
android:layout_gravity="center"
tools:tint="@color/red"/>
</FrameLayout>

View File

@ -1,44 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
<org.thoughtcrime.securesms.scribbles.ScribbleToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"/>
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
android:id="@+id/scribble_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
<FrameLayout android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:background="@color/grey_300"
android:gravity="center">
<org.thoughtcrime.securesms.scribbles.ScribbleHud
android:id="@+id/scribble_hud"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
android:id="@+id/scribble_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"/>
<org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker
android:id="@+id/scribble_color_picker"
android:layout_width="13dp"
android:layout_height="300dp"
android:layout_gravity="top|right"
android:layout_marginRight="25dp"
android:layout_marginTop="20dp"
app:pickerBorderWidth="1dp"
app:pickerBorderColor="@color/grey_600"
app:pickerColors="@array/scribble_colors"/>
</FrameLayout>
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/scribble_save_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|left"
android:src="@drawable/ic_check_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right|top"
android:orientation="vertical"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/scribble_text_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_text_fields_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/scribble_draw_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_brush_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/scribble_highlight_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/baseline_highlight_white_24"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/scribble_sticker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_tag_faces_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/scribble_undo_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_replay_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/scribble_delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_delete_white_24dp"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker
android:id="@+id/scribble_color_picker"
android:layout_width="26dp"
android:layout_height="170dp"
android:layout_gravity="center"
android:layout_marginTop="12dp"
app:pickerBorderWidth="4dp"
app:pickerBorderColor="@color/white"
app:pickerColors="@array/scribble_colors"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/scribble_color_palette"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</merge>

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<merge tools:parentTag="FrameLayout"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView android:id="@+id/image_view"
android:layout_width="wrap_content"

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextSecure.ScribbleTheme" parent="TextSecure.DarkNoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
</resources>

View File

@ -273,11 +273,15 @@
</integer-array>
<array name="scribble_colors">
<item>#000000</item>
<item>#ffffff</item>
<item>#ff0000</item>
<item>#ffff00</item>
<item>#00ffff</item>
<item>#ff00ff</item>
<item>#0000ff</item>
<item>#00ffff</item>
<item>#00ff00</item>
<item>#ffff00</item>
<item>#ff5500</item>
<item>#000000</item>
</array>
<string-array name="pref_message_font_size_entries">

View File

@ -54,7 +54,7 @@
<color name="import_export_touch_highlight_light">#400099cc</color>
<color name="import_export_touch_highlight_dark">#40ffffff</color>
<color name="sticker_selected_color">#8cf437</color>
<color name="sticker_selected_color">#99ffffff</color>
<color name="transparent">#00FFFFFF</color>
<color name="MediaOverview_Media_selected_overlay">#88000000</color>

View File

@ -58,7 +58,7 @@
<dimen name="onboarding_title_size">34sp</dimen>
<dimen name="onboarding_subtitle_size">20sp</dimen>
<dimen name="scribble_stroke_size">3dp</dimen>
<dimen name="scribble_stroke_size">2dp</dimen>
<dimen name="floating_action_button_margin">16dp</dimen>

View File

@ -373,4 +373,7 @@
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
</style>
<style name="TextSecure.ScribbleTheme" parent="TextSecure.DarkNoActionBar">
</style>
</resources>

View File

@ -5,7 +5,6 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PointF;
import android.net.Uri;
import android.os.AsyncTask;
@ -18,7 +17,6 @@ import android.view.View;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
@ -40,41 +38,37 @@ import java.io.IOException;
import java.util.concurrent.ExecutionException;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleToolbar.ScribbleToolbarListener, VerticalSlideColorPicker.OnColorChangeListener {
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleHud.EventListener, VerticalSlideColorPicker.OnColorChangeListener {
private static final String TAG = ScribbleActivity.class.getName();
public static final int SELECT_STICKER_REQUEST_CODE = 123;
public static final int SCRIBBLE_REQUEST_CODE = 31424;
private VerticalSlideColorPicker colorPicker;
private ScribbleToolbar toolbar;
private ScribbleView scribbleView;
private GlideRequests glideRequests;
private ScribbleHud scribbleHud;
private ScribbleView scribbleView;
private GlideRequests glideRequests;
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
setContentView(R.layout.scribble_activity);
this.glideRequests = GlideApp.with(this);
this.scribbleHud = findViewById(R.id.scribble_hud);
this.scribbleView = findViewById(R.id.scribble_view);
this.toolbar = findViewById(R.id.toolbar);
this.colorPicker = findViewById(R.id.scribble_color_picker);
this.toolbar.setListener(this);
this.toolbar.setToolColor(Color.RED);
scribbleHud.setEventListener(this);
scribbleView.setMotionViewCallback(motionViewCallback);
scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors()));
scribbleView.setDrawingMode(false);
scribbleView.setImage(glideRequests, getIntent().getData());
colorPicker.setOnColorChangeListener(this);
colorPicker.setVisibility(View.GONE);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setTitle(null);
if (Build.VERSION.SDK_INT >= 19) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
}
private void addSticker(final Bitmap pica) {
@ -96,6 +90,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
textEntity.getLayer().getFont().setColor(selectedColor);
textEntity.updateEntity();
scribbleView.invalidate();
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
}
private void startTextEntityEditing() {
@ -119,23 +114,21 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
scribbleView.addEntityAndPosition(textEntity);
// move text sticker up so that its not hidden under keyboard
PointF center = textEntity.absoluteCenter();
center.y = center.y * 0.5F;
textEntity.moveCenterTo(center);
// redraw
scribbleView.invalidate();
startTextEntityEditing();
changeTextEntityColor(toolbar.getToolColor());
changeTextEntityColor(scribbleHud.getActiveColor());
}
private TextLayer createTextLayer() {
TextLayer textLayer = new TextLayer();
Font font = new Font();
font.setColor(TextLayer.Limits.INITIAL_FONT_COLOR);
font.setColor(scribbleHud.getActiveColor());
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
textLayer.setFont(font);
@ -150,7 +143,6 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
if (resultCode == RESULT_OK) {
if (requestCode == SELECT_STICKER_REQUEST_CODE) {
if (data != null) {
toolbar.setStickerSelected(true);
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
new AsyncTask<Void, Void, Bitmap>() {
@ -176,44 +168,52 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
}
@Override
public void onBrushSelected(boolean enabled) {
scribbleView.setDrawingMode(enabled);
colorPicker.setVisibility(enabled ? View.VISIBLE : View.GONE);
public void onModeStarted(@NonNull ScribbleHud.Mode mode) {
switch (mode) {
case DRAW:
scribbleView.setDrawingMode(true);
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH);
break;
case HIGHLIGHT:
scribbleView.setDrawingMode(true);
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH * 3);
break;
case TEXT:
scribbleView.setDrawingMode(false);
addTextSticker();
break;
case STICKER:
scribbleView.setDrawingMode(false);
Intent intent = new Intent(this, StickerSelectActivity.class);
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
break;
case NONE:
scribbleView.clearSelection();
scribbleView.setDrawingMode(false);
break;
}
}
@Override
public void onPaintUndo() {
public void onColorChange(int color) {
scribbleView.setDrawingBrushColor(color);
changeTextEntityColor(color);
}
@Override
public void onUndo() {
scribbleView.undoDrawing();
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
}
@Override
public void onTextSelected(boolean enabled) {
if (enabled) {
addTextSticker();
scribbleView.setDrawingMode(false);
colorPicker.setVisibility(View.VISIBLE);
} else {
scribbleView.clearSelection();
colorPicker.setVisibility(View.GONE);
}
}
@Override
public void onStickerSelected(boolean enabled) {
colorPicker.setVisibility(View.GONE);
if (!enabled) {
scribbleView.clearSelection();
} else {
scribbleView.setDrawingMode(false);
Intent intent = new Intent(this, StickerSelectActivity.class);
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
}
}
public void onDeleteSelected() {
public void onDelete() {
scribbleView.deleteSelected();
colorPicker.setVisibility(View.GONE);
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
}
@Override
@ -250,14 +250,14 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
@Override
public void onEntitySelected(@Nullable MotionEntity entity) {
if (entity == null) {
toolbar.setNoneSelected();
colorPicker.setVisibility(View.GONE);
scribbleHud.enterMode(ScribbleHud.Mode.NONE);
} else if (entity instanceof TextEntity) {
toolbar.setTextSelected(true);
colorPicker.setVisibility(View.VISIBLE);
int textColor = ((TextEntity) entity).getLayer().getFont().getColor();
scribbleHud.enterMode(ScribbleHud.Mode.TEXT);
scribbleHud.setActiveColor(textColor);
} else {
toolbar.setStickerSelected(true);
colorPicker.setVisibility(View.GONE);
scribbleHud.enterMode(ScribbleHud.Mode.STICKER);
}
}
@ -266,14 +266,4 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
startTextEntityEditing();
}
};
@Override
public void onColorChange(int color) {
if (color == 0) color = Color.RED;
toolbar.setToolColor(color);
scribbleView.setDrawingBrushColor(color);
changeTextEntityColor(color);
}
}

View File

@ -0,0 +1,250 @@
package org.thoughtcrime.securesms.scribbles;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
import java.util.Set;
/**
* The HUD (heads-up display) that contains all of the tools for interacting with
* {@link org.thoughtcrime.securesms.scribbles.widget.ScribbleView}
*/
public class ScribbleHud extends FrameLayout {
private View drawButton;
private View highlightButton;
private View textButton;
private View stickerButton;
private View undoButton;
private View deleteButton;
private View saveButton;
private VerticalSlideColorPicker colorPicker;
private RecyclerView colorPalette;
private EventListener eventListener;
private ColorPaletteAdapter colorPaletteAdapter;
public ScribbleHud(@NonNull Context context) {
super(context);
initialize();
}
public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.scribble_hud, this);
drawButton = findViewById(R.id.scribble_draw_button);
highlightButton = findViewById(R.id.scribble_highlight_button);
textButton = findViewById(R.id.scribble_text_button);
stickerButton = findViewById(R.id.scribble_sticker_button);
undoButton = findViewById(R.id.scribble_undo_button);
deleteButton = findViewById(R.id.scribble_delete_button);
saveButton = findViewById(R.id.scribble_save_button);
colorPicker = findViewById(R.id.scribble_color_picker);
colorPalette = findViewById(R.id.scribble_color_palette);
undoButton.setOnClickListener(v -> {
if (eventListener != null) {
eventListener.onUndo();
}
});
deleteButton.setOnClickListener(v -> {
if (eventListener != null) {
eventListener.onDelete();
}
setMode(Mode.NONE);
});
saveButton.setOnClickListener(v -> {
if (eventListener != null) {
eventListener.onSave();
}
setMode(Mode.NONE);
});
colorPaletteAdapter = new ColorPaletteAdapter();
colorPaletteAdapter.setEventListener(colorPicker::setActiveColor);
colorPalette.setLayoutManager(new LinearLayoutManager(getContext()));
colorPalette.setAdapter(colorPaletteAdapter);
setMode(Mode.NONE);
}
public void setColorPalette(@NonNull Set<Integer> colors) {
colorPaletteAdapter.setColors(colors);
}
public void enterMode(@NonNull Mode mode) {
setMode(mode, false);
}
private void setMode(@NonNull Mode mode) {
setMode(mode, true);
}
private void setMode(@NonNull Mode mode, boolean notify) {
switch (mode) {
case NONE: presentModeNone(); break;
case DRAW: presentModeDraw(); break;
case HIGHLIGHT: presentModeHighlight(); break;
case TEXT: presentModeText(); break;
case STICKER: presentModeSticker(); break;
}
if (notify && eventListener != null) {
eventListener.onModeStarted(mode);
}
}
private void presentModeNone() {
drawButton.setVisibility(VISIBLE);
highlightButton.setVisibility(VISIBLE);
textButton.setVisibility(VISIBLE);
stickerButton.setVisibility(VISIBLE);
undoButton.setVisibility(GONE);
deleteButton.setVisibility(GONE);
colorPicker.setVisibility(GONE);
colorPalette.setVisibility(GONE);
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
stickerButton.setOnClickListener(v -> setMode(Mode.STICKER));
}
private void presentModeDraw() {
drawButton.setVisibility(VISIBLE);
undoButton.setVisibility(VISIBLE);
colorPicker.setVisibility(VISIBLE);
colorPalette.setVisibility(VISIBLE);
highlightButton.setVisibility(GONE);
textButton.setVisibility(GONE);
stickerButton.setVisibility(GONE);
deleteButton.setVisibility(GONE);
drawButton.setOnClickListener(v -> setMode(Mode.NONE));
colorPicker.setOnColorChangeListener(standardOnColorChangeListener);
colorPicker.setActiveColor(Color.RED);
}
private void presentModeHighlight() {
highlightButton.setVisibility(VISIBLE);
undoButton.setVisibility(VISIBLE);
colorPicker.setVisibility(VISIBLE);
colorPalette.setVisibility(VISIBLE);
drawButton.setVisibility(GONE);
textButton.setVisibility(GONE);
stickerButton.setVisibility(GONE);
deleteButton.setVisibility(GONE);
highlightButton.setOnClickListener(v -> setMode(Mode.NONE));
colorPicker.setOnColorChangeListener(highlightOnColorChangeListener);
colorPicker.setActiveColor(Color.YELLOW);
}
private void presentModeText() {
textButton.setVisibility(VISIBLE);
deleteButton.setVisibility(VISIBLE);
colorPicker.setVisibility(VISIBLE);
colorPalette.setVisibility(VISIBLE);
drawButton.setVisibility(GONE);
highlightButton.setVisibility(GONE);
stickerButton.setVisibility(GONE);
undoButton.setVisibility(GONE);
textButton.setOnClickListener(v -> setMode(Mode.NONE));
colorPicker.setOnColorChangeListener(standardOnColorChangeListener);
colorPicker.setActiveColor(Color.WHITE);
}
private void presentModeSticker() {
stickerButton.setVisibility(VISIBLE);
deleteButton.setVisibility(VISIBLE);
drawButton.setVisibility(GONE);
highlightButton.setVisibility(GONE);
textButton.setVisibility(GONE);
undoButton.setVisibility(GONE);
colorPicker.setVisibility(GONE);
colorPalette.setVisibility(GONE);
stickerButton.setOnClickListener(v -> setMode(Mode.NONE));
}
public int getActiveColor() {
return colorPicker.getActiveColor();
}
public void setActiveColor(int color) {
colorPicker.setActiveColor(color);
}
public void setEventListener(@Nullable EventListener eventListener) {
this.eventListener = eventListener;
}
private final VerticalSlideColorPicker.OnColorChangeListener standardOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() {
@Override
public void onColorChange(int selectedColor) {
if (eventListener != null) {
eventListener.onColorChange(selectedColor);
}
}
};
private final VerticalSlideColorPicker.OnColorChangeListener highlightOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() {
@Override
public void onColorChange(int selectedColor) {
if (eventListener != null) {
int r = Color.red(selectedColor);
int g = Color.green(selectedColor);
int b = Color.blue(selectedColor);
int a = 128;
eventListener.onColorChange(Color.argb(a, r, g, b));
}
}
};
public enum Mode {
NONE, DRAW, HIGHLIGHT, TEXT, STICKER
}
public interface EventListener {
void onModeStarted(@NonNull Mode mode);
void onColorChange(int color);
void onUndo();
void onDelete();
void onSave();
}
}

View File

@ -1,240 +0,0 @@
/**
* Copyright (C) 2016 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.scribbles;
import android.animation.LayoutTransition;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.thoughtcrime.securesms.R;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class ScribbleToolbar extends Toolbar implements View.OnClickListener {
private enum Selected {
NONE,
STICKER,
TEXT,
BRUSH
}
private int foregroundSelectedTint;
private int foregroundUnselectedTint;
private LinearLayout toolsView;
private ImageView saveView;
private ImageView brushView;
private ImageView textView;
private ImageView stickerView;
private ImageView separatorView;
private ImageView undoView;
private ImageView deleteView;
private Drawable background;
@Nullable
private ScribbleToolbarListener listener;
private int toolColor = Color.RED;
private Selected selected = Selected.NONE;
public ScribbleToolbar(Context context) {
super(context);
init(context);
}
public ScribbleToolbar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ScribbleToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
inflate(context, R.layout.scribble_toolbar, this);
this.toolsView = (LinearLayout) findViewById(R.id.tools_view);
this.brushView = (ImageView) findViewById(R.id.brush_button);
this.textView = (ImageView) findViewById(R.id.text_button);
this.stickerView = (ImageView) findViewById(R.id.sticker_button);
this.separatorView = (ImageView) findViewById(R.id.separator);
this.saveView = (ImageView) findViewById(R.id.save);
this.undoView = (ImageView) findViewById(R.id.undo);
this.deleteView = (ImageView) findViewById(R.id.delete);
this.background = getResources().getDrawable(R.drawable.circle_tintable);
this.foregroundSelectedTint = getResources().getColor(R.color.white);
this.foregroundUnselectedTint = getResources().getColor(R.color.grey_800);
this.undoView.setOnClickListener(this);
this.brushView.setOnClickListener(this);
this.textView.setOnClickListener(this);
this.stickerView.setOnClickListener(this);
this.separatorView.setOnClickListener(this);
this.deleteView.setOnClickListener(this);
this.saveView.setOnClickListener(this);
}
public void setListener(@Nullable ScribbleToolbarListener listener) {
this.listener = listener;
}
public void setToolColor(int toolColor) {
this.toolColor = toolColor;
this.background.setColorFilter(new PorterDuffColorFilter(toolColor, PorterDuff.Mode.MULTIPLY));
}
public int getToolColor() {
return this.toolColor;
}
@Override
public void onClick(View v) {
this.toolsView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
if (v == this.brushView) {
boolean enabled = selected != Selected.BRUSH;
setBrushSelected(enabled);
if (listener != null) listener.onBrushSelected(enabled);
} else if (v == this.stickerView) {
setNoneSelected();
if (listener != null) listener.onStickerSelected(true);
} else if (v == this.textView) {
boolean enabled = selected != Selected.TEXT;
setTextSelected(enabled);
if (listener != null) listener.onTextSelected(enabled);
} else if (v == this.deleteView) {
setNoneSelected();
if (listener != null) listener.onDeleteSelected();
} else if (v == this.undoView) {
if (listener != null) listener.onPaintUndo();
} else if (v == this.saveView) {
if (listener != null) listener.onSave();
}
}
private void setBrushSelected(boolean enabled) {
if (enabled) {
this.textView.setBackground(null);
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.brushView.setBackground(background);
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY));
this.stickerView.setBackground(null);
this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.separatorView.setVisibility(View.VISIBLE);
this.undoView.setVisibility(View.VISIBLE);
this.deleteView.setVisibility(View.GONE);
this.selected = Selected.BRUSH;
} else {
this.brushView.setBackground(null);
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.separatorView.setVisibility(View.GONE);
this.undoView.setVisibility(View.GONE);
this.selected = Selected.NONE;
}
}
public void setTextSelected(boolean enabled) {
if (enabled) {
this.brushView.setBackground(null);
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.textView.setBackground(background);
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY));
this.stickerView.setBackground(null);
this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.separatorView.setVisibility(View.VISIBLE);
this.undoView.setVisibility(View.GONE);
this.deleteView.setVisibility(View.VISIBLE);
this.selected = Selected.TEXT;
} else {
this.textView.setBackground(null);
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.separatorView.setVisibility(View.GONE);
this.deleteView.setVisibility(View.GONE);
this.selected = Selected.NONE;
}
}
public void setStickerSelected(boolean enabled) {
if (enabled) {
this.brushView.setBackground(null);
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.textView.setBackground(null);
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
this.separatorView.setVisibility(View.VISIBLE);
this.undoView.setVisibility(View.GONE);
this.deleteView.setVisibility(View.VISIBLE);
this.selected = Selected.STICKER;
} else {
this.separatorView.setVisibility(View.GONE);
this.deleteView.setVisibility(View.GONE);
this.selected = Selected.NONE;
}
}
public void setNoneSelected() {
setBrushSelected(false);
setStickerSelected(false);
setTextSelected(false);
this.selected = Selected.NONE;
}
public interface ScribbleToolbarListener {
public void onBrushSelected(boolean enabled);
public void onPaintUndo();
public void onTextSelected(boolean enabled);
public void onStickerSelected(boolean enabled);
public void onDeleteSelected();
public void onSave();
}
}

View File

@ -88,7 +88,7 @@ public class TextLayer extends Layer {
float FONT_SIZE_STEP = 0.008F;
float INITIAL_FONT_SIZE = 0.075F;
float INITIAL_FONT_SIZE = 0.1F;
int INITIAL_FONT_COLOR = 0xff000000;
float INITIAL_SCALE = 0.8F; // set the same to avoid text scaling

View File

@ -20,15 +20,16 @@ import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* This class defines fields and methods for drawing.
@ -37,6 +38,8 @@ public class CanvasView extends View {
private static final String TAG = CanvasView.class.getSimpleName();
public static final int DEFAULT_STROKE_WIDTH = 15;
// Enumeration for Mode
public enum Mode {
DRAW,
@ -78,7 +81,7 @@ public class CanvasView extends View {
private Paint.Style paintStyle = Paint.Style.STROKE;
private int paintStrokeColor = Color.BLACK;
private int paintFillColor = Color.BLACK;
private float paintStrokeWidth = 15F;
private float paintStrokeWidth = DEFAULT_STROKE_WIDTH;
private int opacity = 255;
private float blur = 0F;
private Paint.Cap lineCap = Paint.Cap.ROUND;
@ -143,7 +146,7 @@ public class CanvasView extends View {
paint.setStyle(this.paintStyle);
paint.setStrokeWidth(this.paintStrokeWidth);
paint.setStrokeCap(this.lineCap);
paint.setStrokeJoin(Paint.Join.MITER); // fixed
paint.setStrokeJoin(Paint.Join.ROUND); // fixed
if (this.mode == Mode.ERASER) {
// Eraser
@ -275,7 +278,9 @@ public class CanvasView extends View {
switch (this.drawer) {
case PEN :
path.lineTo(x, y);
for (int i = 0; i < event.getHistorySize(); i++) {
path.lineTo(event.getHistoricalX(i), event.getHistoricalY(i));
}
break;
case LINE :
path.reset();
@ -770,4 +775,14 @@ public class CanvasView extends View {
return this.getBitmapAsByteArray(CompressFormat.PNG, 100);
}
public @NonNull Set<Integer> getUniqueColors() {
Set<Integer> colors = new LinkedHashSet<>();
for (int i = 1; i < paintLists.size() && i < historyPointer; i++) {
int color = paintLists.get(i).getColor();
colors.add(Color.rgb(Color.red(color), Color.green(color), Color.blue(color)));
}
return colors;
}
}

View File

@ -0,0 +1,73 @@
package org.thoughtcrime.securesms.scribbles.widget;
import android.graphics.PorterDuff;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ColorPaletteAdapter extends RecyclerView.Adapter<ColorPaletteAdapter.ColorViewHolder> {
private final List<Integer> colors = new ArrayList<>();
private EventListener eventListener;
@Override
public ColorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ColorViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_color, parent, false));
}
@Override
public void onBindViewHolder(ColorViewHolder holder, int position) {
holder.bind(colors.get(position), eventListener);
}
@Override
public int getItemCount() {
return colors.size();
}
public void setColors(@NonNull Collection<Integer> colors) {
this.colors.clear();
this.colors.addAll(colors);
notifyDataSetChanged();
}
public void setEventListener(@Nullable EventListener eventListener) {
this.eventListener = eventListener;
notifyDataSetChanged();
}
public interface EventListener {
void onColorSelected(int color);
}
static class ColorViewHolder extends RecyclerView.ViewHolder {
ImageView foreground;
ColorViewHolder(View itemView) {
super(itemView);
foreground = itemView.findViewById(R.id.palette_item_foreground);
}
void bind(int color, @Nullable EventListener eventListener) {
foreground.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (eventListener != null) {
itemView.setOnClickListener(v -> eventListener.onColorSelected(color));
}
}
}
}

View File

@ -36,6 +36,7 @@ import android.support.v4.view.ViewCompat;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
@ -56,7 +57,9 @@ import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity;
import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class MotionView extends FrameLayout implements TextWatcher {
@ -182,6 +185,18 @@ public class MotionView extends FrameLayout implements TextWatcher {
}
}
public @NonNull Set<Integer> getUniqueColors() {
Set<Integer> colors = new LinkedHashSet<>();
for (MotionEntity entity : entities) {
if (entity instanceof TextEntity) {
colors.add(((TextEntity) entity).getLayer().getFont().getColor());
}
}
return colors;
}
private void initEntityBorder(@NonNull MotionEntity entity ) {
// init stroke
int strokeSize = getResources().getDimensionPixelSize(R.dimen.scribble_stroke_size);
@ -274,14 +289,17 @@ public class MotionView extends FrameLayout implements TextWatcher {
}
private void selectEntity(@Nullable MotionEntity entity, boolean updateCallback) {
if (selectedEntity != null) {
if (selectedEntity != null && entity != selectedEntity) {
selectedEntity.setIsSelected(false);
if (selectedEntity instanceof TextEntity) {
editText.clearComposingText();
editText.clearFocus();
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (TextUtils.isEmpty(((TextEntity) selectedEntity).getLayer().getText())) {
deletedSelectedEntity();
} else {
editText.clearComposingText();
editText.clearFocus();
}
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
@ -413,6 +431,12 @@ public class MotionView extends FrameLayout implements TextWatcher {
updateSelectionOnTap(e);
return true;
}
@Override
public boolean onDown(MotionEvent e) {
updateSelectionOnTap(e);
return false;
}
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

View File

@ -21,18 +21,22 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
@ -43,12 +47,15 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import java.util.concurrent.ExecutionException;
import java.util.LinkedHashSet;
import java.util.Set;
public class ScribbleView extends FrameLayout {
private static final String TAG = ScribbleView.class.getSimpleName();
public static final int DEFAULT_BRUSH_WIDTH = CanvasView.DEFAULT_STROKE_WIDTH;
private ImageView imageView;
private MotionView motionView;
private CanvasView canvasView;
@ -77,7 +84,7 @@ public class ScribbleView extends FrameLayout {
}
public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
this.imageUri = uri;
this.imageUri = uri;
glideRequests.load(new DecryptableUri(uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
@ -85,7 +92,6 @@ public class ScribbleView extends FrameLayout {
.into(imageView);
}
@SuppressLint("StaticFieldLeak")
public @NonNull ListenableFuture<Bitmap> getRenderedImage(@NonNull GlideRequests glideRequests) {
final SettableFuture<Bitmap> future = new SettableFuture<>();
final Context context = getContext();
@ -96,43 +102,33 @@ public class ScribbleView extends FrameLayout {
return future;
}
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected @Nullable Bitmap doInBackground(Void... params) {
try {
int width = Target.SIZE_ORIGINAL;
int height = Target.SIZE_ORIGINAL;
int width = Target.SIZE_ORIGINAL;
int height = Target.SIZE_ORIGINAL;
if (isLowMemory) {
width = 768;
height = 768;
}
if (isLowMemory) {
width = 768;
height = 768;
}
return glideRequests.asBitmap()
.load(new DecryptableUri(imageUri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(width, height)
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
return null;
}
}
glideRequests.asBitmap()
.load(new DecryptableUri(imageUri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.override(width, height)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
Canvas canvas = new Canvas(bitmap);
motionView.render(canvas);
canvasView.render(canvas);
future.set(bitmap);
}
@Override
protected void onPostExecute(@Nullable Bitmap bitmap) {
if (bitmap == null) {
future.set(null);
return;
}
Canvas canvas = new Canvas(bitmap);
motionView.render(canvas);
canvasView.render(canvas);
future.set(bitmap);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
future.set(null);
}
});
return future;
}
@ -149,6 +145,18 @@ public class ScribbleView extends FrameLayout {
this.motionView.setMotionViewCallback(callback);
}
@SuppressLint("ClickableViewAccessibility")
public void setDrawingChangedListener(@Nullable DrawingChangedListener listener) {
this.canvasView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
if (listener != null) {
listener.onDrawingChanged();
}
}
return false;
});
}
public void setDrawingMode(boolean enabled) {
this.canvasView.setActive(enabled);
if (enabled) this.motionView.unselectEntity();
@ -157,6 +165,11 @@ public class ScribbleView extends FrameLayout {
public void setDrawingBrushColor(int color) {
this.canvasView.setPaintFillColor(color);
this.canvasView.setPaintStrokeColor(color);
this.canvasView.setOpacity(Color.alpha(color));
}
public void setDrawingBrushWidth(int width) {
this.canvasView.setPaintStrokeWidth(width);
}
public void addEntityAndPosition(MotionEntity entity) {
@ -183,6 +196,15 @@ public class ScribbleView extends FrameLayout {
this.motionView.startEditing(entity);
}
public @NonNull Set<Integer> getUniqueColors() {
Set<Integer> colors = new LinkedHashSet<>();
colors.addAll(motionView.getUniqueColors());
colors.addAll(canvasView.getUniqueColors());
return colors;
}
@Override
public void onMeasure(int width, int height) {
super.onMeasure(width, height);
@ -196,4 +218,7 @@ public class ScribbleView extends FrameLayout {
MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
public interface DrawingChangedListener {
void onDrawingChanged();
}
}

View File

@ -43,8 +43,12 @@ import org.thoughtcrime.securesms.R;
public class VerticalSlideColorPicker extends View {
private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.8f;
private Paint paint;
private Paint strokePaint;
private Paint indicatorStrokePaint;
private Paint indicatorFillPaint;
private Path path;
private Bitmap bitmap;
private Canvas bitmapCanvas;
@ -59,8 +63,12 @@ public class VerticalSlideColorPicker extends View {
private int borderColor;
private float borderWidth;
private float indicatorRadius;
private int[] colors;
private int touchY;
private int activeColor;
public VerticalSlideColorPicker(Context context) {
super(context);
init();
@ -74,9 +82,9 @@ public class VerticalSlideColorPicker extends View {
try {
int colorsResourceId = a.getResourceId(R.styleable.VerticalSlideColorPicker_pickerColors, R.array.scribble_colors);
colors = a.getResources().getIntArray(colorsResourceId);
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
colors = a.getResources().getIntArray(colorsResourceId);
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
} finally {
a.recycle();
@ -110,6 +118,13 @@ public class VerticalSlideColorPicker extends View {
strokePaint.setColor(borderColor);
strokePaint.setAntiAlias(true);
strokePaint.setStrokeWidth(borderWidth);
indicatorStrokePaint = new Paint(strokePaint);
indicatorStrokePaint.setStrokeWidth(borderWidth / 2);
indicatorFillPaint = new Paint();
indicatorFillPaint.setStyle(Paint.Style.FILL);
indicatorFillPaint.setAntiAlias(true);
}
@Override
@ -126,20 +141,27 @@ public class VerticalSlideColorPicker extends View {
bitmapCanvas.drawPath(path, paint);
canvas.drawBitmap(bitmap, 0, 0, null);
touchY = Math.max((int) colorPickerBody.top, touchY);
indicatorFillPaint.setColor(activeColor);
canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorFillPaint);
canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorStrokePaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
touchY = (int) Math.min(event.getY(), colorPickerBody.bottom);
touchY = (int) Math.max(colorPickerBody.top, touchY);
float yPos = Math.min(event.getY(), colorPickerBody.bottom);
yPos = Math.max(colorPickerBody.top, yPos);
int selectedColor = bitmap.getPixel(viewWidth/2, (int) yPos);
activeColor = bitmap.getPixel(viewWidth/2, touchY);
if (onColorChangeListener != null) {
onColorChangeListener.onColorChange(selectedColor);
onColorChangeListener.onColorChange(activeColor);
}
invalidate();
return true;
}
@ -147,13 +169,16 @@ public class VerticalSlideColorPicker extends View {
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewWidth = w;
viewHeight = h;
centerX = viewWidth / 2;
colorPickerRadius = (viewWidth / 2) - borderWidth;
int barWidth = (int) (viewWidth * INDICATOR_TO_BAR_WIDTH_RATIO);
colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius));
centerX = viewWidth / 2;
indicatorRadius = (viewWidth / 2) - borderWidth;
colorPickerRadius = (barWidth / 2) - borderWidth;
colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius));
LinearGradient gradient = new LinearGradient(0, colorPickerBody.top, 0, colorPickerBody.bottom, colors, null, Shader.TileMode.CLAMP);
paint.setShader(gradient);
@ -164,8 +189,6 @@ public class VerticalSlideColorPicker extends View {
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
resetToDefault();
}
public void setBorderColor(int borderColor) {
@ -183,20 +206,29 @@ public class VerticalSlideColorPicker extends View {
invalidate();
}
public void resetToDefault() {
public void setActiveColor(int color) {
activeColor = color;
if (colorPickerBody != null) {
touchY = (int) colorPickerBody.top;
}
if (onColorChangeListener != null) {
onColorChangeListener.onColorChange(Color.RED);
onColorChangeListener.onColorChange(color);
}
invalidate();
}
public int getActiveColor() {
return activeColor;
}
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
this.onColorChangeListener = onColorChangeListener;
}
public interface OnColorChangeListener {
void onColorChange(int selectedColor);
}
}