Android TextView Long Shadow - java

I've been trying this since morning, yet I can't get it to work.
What I'm trying to do is create a somewhat like long shadow for the TextView, which is similar to the following:
http://www.iceflowstudios.com/v3/wp-content/uploads/2013/07/long_shadow_banner.jpg
http://web3canvas.com/wp-content/uploads/2013/07/lsd-ps-action-720x400.png
My solution so far was to create a lot of TextViews and cascade them under each other, but there are a lot of performance issues if I go with the current way.
Another solution is the usage of a custom font that has that similar allure, yet I cannot find any that matches the font I am currently using.
So I was wondering, is it possible to use: (I have to mention, the textviews are created dynamically)
TV.setShadowLayer(1f, 5f, 5f, Color.GREY);
To create several of them in a line (as a cascading layer), making the shadow seem smooth? Or do you guys suggest any other solutions?
Thanks in advance.

Try to play with raster images:
Detect bounds of text using Paint.getTextBounds() method
Create transparent Bitmap with such metrics (W + H) x H (you may use Bitmap.Config.ALPHA_8 to optimize memory usage)
Draw text on this Bitmap at 0x0 position
Copy first row of Bitmap into new one with original width, but with height of 1px
Iterate over the Y-axis of Bitmap (from top to bottom) and draw single-line Bitmap with the corresponding offset by X-axis (you will overdraw some transparent pixels)
Now you have the top-part of your shadow
Draw the bottom part using same technique, but choosing last row of this Bitmap
This algorithm may be optimized if you detect, that all pixels in last row have the same color (full shadow).
UPDATE 1
I achieved such result using this quick solution:
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
LongShadowTextView longShadow = new LongShadowTextView(this);
longShadow.setText("Hello World");
setContentView(longShadow);
}
}
LongShadowTextView.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
public class LongShadowTextView extends View {
private Bitmap mBitmap;
private String mText;
public LongShadowTextView(Context context) {
super(context);
}
public void setText(String text) {
Paint paint = new Paint();
// TODO provide setters for these values
paint.setColor(Color.BLACK);
paint.setTextSize(142);
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
Bitmap bitmap = Bitmap.createBitmap(rect.width() + rect.height(), rect.height(), Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(text, 0, rect.height(), paint);
Rect src = new Rect();
RectF dst = new RectF();
int w = bitmap.getWidth();
int h = bitmap.getHeight();
src.left = 0;
src.right = w;
for (int i = 0; i < h; ++i) {
src.top = i;
src.bottom = i + 1;
dst.left = 1;
dst.top = i + 1;
dst.right = 1 + w;
dst.bottom = i + 2;
canvas.drawBitmap(bitmap, src, dst, null);
}
mText = text;
mBitmap = bitmap;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
UPDATE 2
Here is final result which I achieved. Clone this demo from github.

I'm afraid your suggested approach of using setShadowLayer() wont work as this approach effectively draws a second TextPaint with blurring.
Superimposing several TextPaints on top of each other will essentially mean you need to offset it by 1px for each step, which is very graphically intensive and will have a very poor performance.
This is an excellent question and a real challenge!
The only solution that comes to mind is to handle each glyph independently, inspecting all path elements and extending a shadow between the furthest bottom-left and top-right point. This seems very complicated, and I don't know if there's any mechanics in the SDK that facilitates an approach like that.
Suggested reading:
This question tackles obtaining glyph paths from TTFs.
This answer illustrates how you can leverage using paths, although it concerns a JavaScript approach.

Small comment if someone would try to run setText() method. it is not working now.
You should call invalidate(); in setText(); method
public void setText(String value) {
boolean changed =
mText == null && value != null || mText != null && !mText.equals(value);
mText = value;
if (changed) {
refresh();
}
invalidate();
}

Related

How to manipulate pixels in a Bitmap?

I am developing an Instagram like application for learning image processing and android. But I am stuck, I have a problem implementing Grayscale Filter in my application. I am trying a simple approach for now to convert individual pixels in a Bitmap to Grayscale.
Here's the whole class I am writing to apply various filters to an Image:
package com.dosa2.photoeditor.ImageEffects;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
public class ImageEffects {
Bitmap bitmap;
int width, height;
public ImageEffects(Bitmap bitmap) {
this.bitmap = bitmap;
width = bitmap.getWidth();
height = bitmap.getHeight();
}
public Bitmap toGrayscale() {
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(resultBitmap);
Paint p = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
p.setColorFilter(f);
c.drawBitmap(bitmap, 0, 0, p);
return resultBitmap;
}
public Bitmap toGrayscale2() {
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
for(int i=0;i<height;i++) {
for (int j=0;i<width;j++) {
int c = bitmap.getPixel(i,j);
resultBitmap.setPixel(i, j, (Color.red(c)+Color.blue(c)+Color.green(c)/3));
}
}
return resultBitmap;
}
}
I have tried 2 methods to convert the Bitmap into Grayscale. The former seems to be working(but I am not able to understand it) and the latter is not.
Can anyone help me out? And do mention if there's an easier way to manipulate Images in Android.
The error (or at least one of them...) is in one of your for loops:
for (int j=0;i<width;j++)
should be
for (int j=0;j<width;j++)
to prevent an indefinite loop.
Your method "toGrayscale" is using the ColorMatrix class, which I think internally uses the RenderScript API to do the rendering (or at least GPU shaders). RenderScript is the Android's computing API (which is similar to OpenCL, in fact, is a layer that works on OpenCL), so you are not using only the CPU to do the color filtering, you are using even the GPU or other DSPs your device may have. The second method "toGrayscale2" is slower because you are using only the CPU to convert your Bitmap to grayscale (pixel by pixel) and you shouldn't use it. Check this presentation (it's very intersting) in order to understand a bit more how your first method works, the link points to the page 12 which is about color filtering, but you should view it entirely in order to understand better.

Applying Reflection to DropShadow Effect in JavaFX

Using JavaFX, I am trying to draw text onto a canvas with a drop shadow and a reflection effect chained together.
The following code will display red text that is reflected, and then a drop shadow is applied to the original and reflected text.
Canvas canvas = new Canvas(400,400);
GraphicsContext context = canvas.getGraphicsContext2D();
context.setFont( new Font("Arial Bold", 48) );
context.setFill(Color.RED);
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
Reflection reflect = new Reflection(10, 1.0, 1.0, 0.0);
shadow.setInput(reflect);
context.setEffect(shadow);
context.fillText("Hello, world!", 100,100);
However, the drop shadow appears "backwards" in the reflection, because the shadow needs to be applied first for a realistic effect. I tried to accomplish this by reversing the order in which the effects are applied, by changing the setInput and setEffect lines of code from above as follows:
reflect.setInput(shadow);
context.setEffect(reflect);
However, the result is that only the reflection is applied; I can not see any drop shadow at all.
Why isn't the drop shadow being applied / not visible?
How can I rewrite this code to achieve the desired effect (only using composition of effects, if possible)?
I don't know if what you say can be achieved by using GraphicsContext with standard API, so other answers are welcome. However, this might be a temporary workaround, provided that it is actually what you needed. The shadow is applied first and then the image is copied pixel by pixel to imitate the reflection effect. Please see the screenshot below: (original - on the left, new - on the right).
Full working example is attached below. It requires some tweaking to get a general solution but there should be enough to start with.
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class FXApp extends Application {
private Parent createContent() {
Canvas canvas = new Canvas(400,400);
Font font = new Font("Arial Bold", 48);
Color fill = Color.RED;
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
fillTextWithReflection(canvas.getGraphicsContext2D(), "Hello, world!", 100, 100, font, fill, shadow);
return new Pane(canvas);
}
private void fillTextWithReflection(GraphicsContext g, String text, double textX, double textY, Font font, Color fill, Effect effect) {
Text t = new Text(text);
t.setFont(font);
// 5 px margin
Canvas tmpCanvas = new Canvas(t.getLayoutBounds().getWidth() + 5, t.getLayoutBounds().getHeight() + 5);
// set configuration
GraphicsContext tmpContext = tmpCanvas.getGraphicsContext2D();
tmpContext.setFont(font);
tmpContext.setFill(fill);
tmpContext.setEffect(effect);
// draw on temporary context
tmpContext.fillText(text, 0, font.getSize());
// take a snapshot of the text
WritableImage snapshot = tmpCanvas.snapshot(null, null);
int w = (int)snapshot.getWidth();
int h = (int)snapshot.getHeight();
WritableImage reflected = new WritableImage(w, h);
// make an 'inverted' copy
for (int y = 0; y < h; y++) {
// imitate fading out of reflection
double alpha = y / (h - 1.0);
for (int x = 0; x < w; x++) {
Color oldColor = snapshot.getPixelReader().getColor(x, y);
Color newColor = Color.color(oldColor.getRed(), oldColor.getGreen(), oldColor.getBlue(), alpha);
reflected.getPixelWriter().setColor(x, h - 1 - y, newColor);
}
}
// draw on the actual context
// images are drawn from x, y top-left but text is filled from x, y + h
// hence corrections
// this can be replaced with actual fillText() call if required
g.drawImage(snapshot, textX, textY - font.getSize());
g.drawImage(reflected, textX, textY + h - font.getSize());
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
}

Why on earth am I getting unfilled space between the edge of my shape and the fill of the same shape?

As part of an Android Wear watch face I started putting together, I have a class which implements rendering for one hand of a watch. It looks like this (after I edited out the bits which aren't relevant):
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
class Hand {
private final float centerX;
private final float centerY;
private final Path untransformedPath;
private final Path path = new Path();
private final Paint fillPaint;
private final Matrix matrix = new Matrix();
Hand2(Context context,
int widthId, int fillColorId, int strokeWidthId,
float centerX, float centerY, float handLength) {
this.centerX = centerX;
this.centerY = centerY;
float halfHandWidth = context.getResources().getDimension(widthId) / 2;
int fillColor = Workarounds.getColor(context, fillColorId);
fillPaint = new Paint();
fillPaint.setColor(fillColor);
fillPaint.setStrokeWidth(context.getResources().getDimension(strokeWidthId));
fillPaint.setStrokeCap(Paint.Cap.BUTT);
fillPaint.setStyle(Paint.Style.FILL_AND_STROKE);
fillPaint.setAntiAlias(true);
untransformedPath = new Path();
float centreRadius = context.getResources().getDimension(R.dimen.analog_centre_hand_start_radius);
float centreDegrees = (float) Math.toDegrees(Math.asin(halfHandWidth / centreRadius));
float startOffset = (float) Math.sqrt(centreRadius * centreRadius - halfHandWidth * halfHandWidth);
float centreStartDegrees = 90.0f - centreDegrees;
float centreSweepDegrees = 2.0f * centreDegrees;
untransformedPath.moveTo(-halfHandWidth, startOffset);
untransformedPath.lineTo(-halfHandWidth, handLength - halfHandWidth);
untransformedPath.lineTo(0, handLength);
untransformedPath.lineTo(halfHandWidth, handLength - halfHandWidth);
untransformedPath.lineTo(halfHandWidth, startOffset);
untransformedPath.arcTo(
-centreRadius, -centreRadius, centreRadius, centreRadius,
centreStartDegrees, centreSweepDegrees, false);
untransformedPath.close();
}
void updateAngle(float angleDegrees) {
matrix.reset();
matrix.setRotate(angleDegrees, 0, 0);
matrix.postTranslate(centerX, centerY);
untransformedPath.transform(matrix, path);
}
Path getPath() {
return path;
}
void draw(Canvas canvas) {
canvas.drawPath(path, fillPaint);
}
void updateHighQuality(boolean highQuality) {
fillPaint.setAntiAlias(highQuality);
}
}
I had noticed the rendering appeared to flicker for some reason and no amount of double buffering appeared to solve it. So I did a crazy thing and hit screenshot many times in a row and eventually caught a screenshot of what's going on:
You can see something like a black line going around one of the hands. If you look carefully, it isn't actually a black line, but rather, it's as if there is a gap in what got filled which is right next to the border of the shape.
What exactly is going on here? I'm rendering a single shape which is FILL_AND_STROKE, so it seems kind of crazy that it would somehow be leaving any of that shape unpainted. And yet, that is exactly what it appears to be doing.
If you're wondering about the glow painted below the hands, that is a separate thing rendered on the background by another class before the hands are drawn, which is why you don't see me doing that here. (The getPath() method is the way which that other class grabs the current path so that it knows where to put the glow.)
Further investigation:
Reversing the order of the vertices in the path has no effect.
Removing the curve in the path does appear to stop whatever is happening, interestingly.
Filling and stroking separately using exactly the same settings also appears to stop it happening.

The View got Pixelated during Animation to Tilt

The view got pixilated during animation I just wanted to attain a little tilt while the I try to scroll. I am using the Universal-Image-Library to hanle the animation. I'd like to attain a 3D look when tilting the view.
The first picture, is what I want.
But this picture below, I what I have. The View below got pixilated.
private void rotateLeftFrag(View af) {
if (af != null) {
ObjectAnimator.ofFloat(af, "rotationY", 5, 0)
.setDuration(100).start();
}
}
ObjectAnimator com.nineoldandroids.animation.ObjectAnimator.ofFloat(Object target, String
propertyName, float... values)
Are there any resolve to this to attain smooth animation or titling of the view? Thanks
Update:
float density = getResources().getDisplayMetrics().density;
float scale = getResources().getDisplayMetrics().density;
af.setCameraDistance(density * scale);
ObjectAnimator.ofFloat(af, "rotationY", .5f, 0).setDuration(500).start();
I think this video could help you:
http://www.youtube.com/watch?v=pMcu35-tVls
At 2:10 the guy talks about adding 1 extra transparent pixel to each side of a rotating rectangle. That should help smoothing out the edges because they would be inside the rectangle, not on the border.
Link to the source code is below the video.
In case you can't see it:
http://developer.android.com/shareables/devbytes/CardFlip.zip
Class you want to see is CardView, method bitmapWithBorder:
private static final int ANTIALIAS_BORDER = 1;
/**
* Adding a 1 pixel transparent border around the bitmap can be used to
* anti-alias the image as it rotates.
*/
private BitmapDrawable bitmapWithBorder(BitmapDrawable bitmapDrawable) {
Bitmap bitmapWithBorder = Bitmap.createBitmap(bitmapDrawable.getIntrinsicWidth() +
ANTIALIAS_BORDER * 2, bitmapDrawable.getIntrinsicHeight() + ANTIALIAS_BORDER * 2,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapWithBorder);
canvas.drawBitmap(bitmapDrawable.getBitmap(), ANTIALIAS_BORDER, ANTIALIAS_BORDER, null);
return new BitmapDrawable(getResources(), bitmapWithBorder);
}
Please try to turn off hardware rendering
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
mHeaderImage.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Corrupted EditText backgrounds when using custom background drawables

I'm working on the android half of a cross-platform android/ios framework that lets you write apps in JS that work on both platforms. I say this because it means I can't use things like 9-patches to get this effect. Full code at https://github.com/mschulkind/cordova-true-native-android
Here are two screenshots of the problem:
-Images redacted because I'm too new to be this useful. I will have to add them when I'm no longer a newbie.-
Here's the code that generates the drawable from https://github.com/mschulkind/cordova-true-native-android/blob/master/src/org/apache/cordova/plugins/truenative/ViewPlugin.java#L146
// Borrowed from:
// http://www.betaful.com/2012/01/programmatic-shapes-in-android/
private class ViewBackground extends ShapeDrawable {
private final Paint mFillPaint, mStrokePaint;
private final int mBorderWidth;
public ViewBackground(
Shape s, int backgroundColor, int borderColor, int borderWidth) {
super(s);
mFillPaint = new Paint(this.getPaint());
mFillPaint.setColor(backgroundColor);
mStrokePaint = new Paint(mFillPaint);
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setStrokeWidth(borderWidth);
mStrokePaint.setColor(borderColor);
mBorderWidth = borderWidth;
}
#Override
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
shape.resize(canvas.getClipBounds().right, canvas.getClipBounds().bottom);
Matrix matrix = new Matrix();
matrix.setRectToRect(
new RectF(
0, 0,
canvas.getClipBounds().right, canvas.getClipBounds().bottom),
new RectF(
mBorderWidth/2, mBorderWidth/2,
canvas.getClipBounds().right - mBorderWidth/2,
canvas.getClipBounds().bottom - mBorderWidth/2),
Matrix.ScaleToFit.FILL);
canvas.concat(matrix);
shape.draw(canvas, mFillPaint);
if (mBorderWidth > 0) {
shape.draw(canvas, mStrokePaint);
}
}
}
This has happened both when the drawable was set as the background of the EditText directly and when I set it as the background of a parent view around the EditText.
Anyone have an idea of what's going on here or what avenues I should explore?
Looks like you want to draw a rounded rectangle.
To achieve such a style, it is simpler to use a XML drawable.
You simply put a XML file into the drawable/ directory. Here you can describe the desired shape.
Some documentation about XML drawables is here : http://idunnolol.com/android/drawables.html
Look at the tag.

Categories

Resources