Good morning,
A portion of my apps features the ability to write down some hand written notes. I am using pieces from the fingerpaint example to implement the "eraser" in my app:
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mPaint.setStrokeWidth(45);
mPaint.setStrokeCap(Paint.Cap.ROUND);
The problem is whenever I erase there is a black path where the user has erased. It is gone once the user picks up their finger, but it is there while they are erasing. My friend tells me that PorterDuff modes are not supposed to be used in real time, but I don't believe him. Here's some more of my code just in case I did something dumb:
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(false);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeWidth(6);
mBitmap = Bitmap.createBitmap(800, 480,Bitmap.Config.ARGB_8888);
bBitmap = Bitmap.createBitmap(800, 480,Bitmap.Config.ARGB_8888);
bBitmap.eraseColor(Color.WHITE);
mCanvas = new Canvas(mBitmap);
mPath = new Path();
mBitmapPaint = new Paint();
maxX = maxY = 100;
minX = 750;
minY = 400;
//
private void touch_up() {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
//
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bBitmap, 0, 0, null);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
Is it possible to do what I want this eraser to do? Is there a better way to do this? Is my friend actually right?
Also I tried just painting white as an erase but it's not going to work because sometimes there is an image underneath that the user is marking up. I could go through and make every white pixel transparent, but that seems super inefficient.
I have found a way to do this properly, it does the erasing with your finger, without the black stroke.
Here is my solution :
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(isEraseMode()){
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
if(isEraseMode()){
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
mPath.moveTo(x, y);
}
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
Ok so I figured out a solution. I'll leave this here for people running into a similar problem:
case MotionEvent.ACTION_DOWN:
mPaint.setStrokeWidth(25);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mCanvas.drawCircle(x, y, 10, mPaint);
isErase = true;
invalidate();
}
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if(isErase)
{
mCanvas.drawCircle(x, y, 20, mPaint);
}
else{
touch_move(x, y);
}invalidate();
break;
I have the same question.
finally, I figured out the solution.
don't use the canvas passed from onDraw(),use the mCanvas created in constructor instead.
(Workaround) it will cost you opasity.
after initialising the canvas, maybe in onCreate of your activity.
myCanvasView.setAlpha(0.99f);
I agree with #Rovers, just change the canvas in this way:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
if (!erase) {
canvas.drawPath(drawPath, drawPaint);
} else {
drawCanvas.drawPath(drawPath, drawPaint);
}
}
There are two kind of canvas into onDraw method --> drawCanvas and canvas.
Related
i'm working on a project where i create mirror of drawing. my main logic is working fine.Only thing causing Problem is Redo and undo functionality.
i have searched a lot. implement may methods but couldn't get success. Following is my Drawing Class.
DrawingView.java
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
this.context=context;
setupDrawing();
}
//setup drawing
private void setupDrawing(){
//prepare for drawing and setup paint stroke properties
brushSize = getResources().getInteger(R.integer.small_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPath1 = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
width=w;
height=h;
Log.d("width,height", w + " , " + h);
drawCanvas = new Canvas(canvasBitmap);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths){canvas.drawPath(p, drawPaint);}
canvas.drawPath(drawPath, drawPaint);
Log.i("OnDRAWING", "REACH ON DRAW");
/*canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
canvas.drawPath(drawPath1, drawPaint);*/
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.reset();
undonePaths.clear();
drawPath.moveTo(touchX, touchY);
undonePaths.clear();
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);// commit the path to our offscreen
paths.add(drawPath);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawCanvas.drawPath(drawPath1, drawPaint);
drawPath1.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
What i am missing here?
Any suggestions/ideas/examples which is the best way to implement this kind of functionality on my project?
Canvas drawing is sort of hierarchal in that drawing happens in the order that you do them.
So all drawing should be done in onDraw and only here.
Your draw events should be pushed on a draw stack. You do not store a canvas. You just store the operations that are supposed to happen when the drawing eventually occurs (coordinates, width, and color of a path for example).
An "undo" operation can be done by popping from the drawing stack and pushing the event to a "redo" stack. The "redo" event can be done by popping from the "redo" stack and pushing back on to the "drawing" stack.
In onDraw method of your View, just scan through the drawing events and draw on the canvas.
EDIT:
The onDraw() method would just iterate through your drawing operations. So first you could have an interface called DrawEvent like so:
public interface DrawEvent {
void draw(Canvas c);
}
Then in your View class have your collection of DrawEvents and iterate through them in onDraw(Canvas c).
public class MyView extends View {
Deque<DrawEvent> drawEvents = new LinkedList<DrawEvent>();
#Override
public void onDraw(Canvas c) {
for (DrawEvent e : drawEvents) {
e.draw(c);
}
}
}
I am trying to make an application where user can either choose to blur the image or can choose to paint on the screen (user can also do both of these at one canvas).
I have it pretty much working however, I am having a strange issue with the drawing on a first draw after the mode is changed from blur to paint or vice versa.
Please see the image below.
PAINT MODE
The paths drawn vertically is when user has paint mode selected. As you can see the first path contains paint from both blur paint object as well paint object (with red stroke). Any subsequent paths drawn now works fine.
BLUR MODE
Similarly you can see, after drawing two vertical paths, user switches the mode to blur and draws horizontal paths in this mode. This time similar to above first path is a mixture of two paint objects and subsequent paths works fine.
Please see the code posted below and It would be great if you can suggest what could be causing the problem.
ArrayList<DrawCommands> path_color_stroke_list = new ArrayList<DrawCommands>();
ArrayList<DrawCommands> path_color_stroke_list_undone = new ArrayList<DrawCommands>();
ArrayList<BlurCommands> path_blur_list = new ArrayList<BlurCommands>();
ArrayList<BlurCommands> path_blur_list_undone = new ArrayList<BlurCommands>();
ArrayList<EditTextDrawCommands> editTextList = new ArrayList<EditTextDrawCommands>();
private Bitmap mBitmap;
private Paint transparentPaint;
private Paint mPaint;
public DrawingPanel(Context context, String imageStorageDir) {
super(context);
appContext = context;
setFocusable(true);
setFocusableInTouchMode(true);
setClickable(true);
this.setOnTouchListener(this);
mPath = new Path();
setDefaultPaintAttributes();
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 8;
blurRefImage = BitmapFactory.decodeResource(getResources(), R.drawable.canvas_test, bmOptions);
canvasBackImage = BitmapFactory.decodeResource(getResources(), R.drawable.canvas_test);
//stretch this small image to the size of the device so that it will be stretched and will already be blurred
blurRefImage = Bitmap.createScaledBitmap(blurRefImage, Utilities.getDeviceWidth(), Utilities.getDeviceHeight(), true);
blurRefImage = BlurBuilder.blurFullImage(appContext, blurRefImage, 20);
mBitmap = Bitmap.createBitmap(Utilities.getDeviceWidth(), Utilities.getDeviceHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
protected void setDefaultPaintAttributes() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(30);
//mPaint.setColor(0xcc000000);
transparentPaint = new Paint();
transparentPaint.setStyle(Paint.Style.STROKE);
transparentPaint.setStrokeJoin(Paint.Join.ROUND);
transparentPaint.setStrokeCap(Paint.Cap.ROUND);
transparentPaint.setStrokeWidth(60);
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
protected void onDraw(Canvas canvas) {
mCanvas.drawBitmap(canvasBackImage, 0, 0, mPaint);
//Draw Blur
for (BlurCommands path_blur : path_blur_list) {
mCanvas.drawPath(path_blur.getPath(), transparentPaint);
}
//Draw Paints
for (DrawCommands path_clr : path_color_stroke_list) {
mCanvas.drawPath(path_clr.getPath(), mPaint);
}
switch (CURRENT_MODE) {
case MODE_BLUR:
mCanvas.drawPath(mPath, transparentPaint);
break;
case MODE_PAINT:
mCanvas.drawPath(mPath, mPaint);
break;
}
canvas.drawBitmap(blurRefImage, 0, 0, mPaint);
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
}
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
switch (CURRENT_MODE) {
case MODE_BLUR:
break;
case MODE_PAINT:
break;
default:
break;
}
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
switch (CURRENT_MODE) {
case MODE_BLUR:
case MODE_PAINT:
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
break;
default:
break;
}
}
}
private void touch_up(MotionEvent event) {
switch (CURRENT_MODE) {
case MODE_BLUR:
mPath.lineTo(mX, mY);
mPath = new Path();
path_blur_list.add(new BlurCommands(mPath, blurStrength, transparentPaint.getStrokeWidth()));
break;
case MODE_PAINT:
mPath.lineTo(mX, mY);
mPath = new Path();
path_color_stroke_list.add(new DrawCommands(mPath, mPaint.getColor(), mPaint.getStrokeWidth()));
Log.d(TAG, "Touch up: X: " + mX + " Y: " + mY);
break;
default:
break;
}
}
You probably want to switch the order of the 2 lines in touch_up that clear the path (new Path), and that add ad the path to the list (first add, then clear)
The error occurs because the mPath object is created matching the previous drawing mode since it was created on mouse-up with the previous drawing mode.
Move the mPath creation to touch_start and the current drawing mode will be used:
private void touch_start(float x, float y) {
mPath = new Path();
mPath.moveTo(x, y);
mX = x;
mY = y;
switch (CURRENT_MODE) {
case MODE_BLUR:
path_blur_list.add(new BlurCommands(mPath, blurStrength, transparentPaint.getStrokeWidth()));
break;
case MODE_PAINT:
path_color_stroke_list.add(new DrawCommands(mPath, mPaint.getColor(), mPaint.getStrokeWidth()));
break;
default:
break;
}
}
...
private void touch_up(MotionEvent event) {
mPath.lineTo(mX, mY);
}
I want to draw a rectangle. The first corner should be the point where user first touches screen. When user moves his finger, it should draw the rectangle. Here's a link that shows a video what I want to do. But I don't understand it and maybe You can help me. I just want to draw that rectangle on a white background not on an image.
My code :
package com.example.androiddrawing;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class CanvasView extends View {
private Canvas canvas;
private Paint paint = new Paint();
private Paint paint2 = new Paint();
private Paint paint3 = new Paint();
private Path path = new Path();
private Point point = new Point();
private static List<Path> lines = new ArrayList<Path>();
private static List<Point> points = new ArrayList<Point>();
private float x, x2, xc, xd, x3, x4;
private float y, y2, yc, yd, y3, y4;
private boolean touchStarted = false;
public enum DrawMode {
FreeDrawMode, RectDrawMode
};
public static DrawMode currentDrawMode;
public void setDrawMode(DrawMode newDrawMode) {
this.currentDrawMode = newDrawMode;
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint2.setAntiAlias(true);
paint2.setStrokeWidth(5);
paint2.setColor(Color.RED);
paint2.setStyle(Paint.Style.STROKE);
paint2.setStrokeJoin(Paint.Join.ROUND);
paint3.setAntiAlias(true);
paint3.setColor(Color.BLACK);
paint3.setStrokeWidth(10);
paint3.setStyle(Paint.Style.STROKE);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : lines)
canvas.drawPath(p, paint);
canvas.drawPath(path, paint2);
for (Point point : points)
canvas.drawCircle(point.x, point.y, 0, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
System.out.println(currentDrawMode);
if (currentDrawMode == DrawMode.FreeDrawMode) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
paint2.setColor(Color.RED);
path = new Path();
path.moveTo(x, y);
touchStarted = true;
break;
// return true;
case MotionEvent.ACTION_MOVE:
// Connect the points
touchStarted = false;
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
if (touchStarted) {
point = new Point();
point.x = (int) x;
point.y = (int) y;
paint2.setColor(Color.BLACK);
points.add(point);
touchStarted = false;
System.out.println("siin");
} else {
System.out.println("seal");
paint2.setColor(Color.BLACK);
lines.add(path);
}
break;
default:
return false;
}
} else if (currentDrawMode == DrawMode.RectDrawMode) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
paint3.setColor(Color.RED);
//CODE HERE
break;
// return true;
case MotionEvent.ACTION_MOVE:
//CODE HERE
break;
case MotionEvent.ACTION_UP:
//CODE HERE
break;
default:
return false;
}
}
// Makes our view repaint and call onDraw
invalidate();
return true;
}
}
I should write the code where I put the comments //CODE HERE, but I really don't understand, how do I have to draw a rectangle.
You can use the below code. Hope this helps you.
public class DrawSample extends View {
int mStartX;
int mStartY;
int mEndX;
int mEndY;
Paint mPaint = new Paint();
int mSelectedColor = Color.BLACK;
public DrawSample(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mPaint.setColor(mSelectedColor);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
setFocusable(true);
}
public DrawSample(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mStartX = (int) event.getX();
mStartY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
mEndX = (int) event.getX();
mEndY = (int) event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
mEndX = (int) event.getX();
mEndY = (int) event.getY();
invalidate();
break;
default:
super.onTouchEvent(event);
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mStartX, mStartY, mEndX, mEndY, mPaint);
}
}
You need to keep the start X and Y points, then, when user stop drawing in the MotionEvent.ACTION_UP get the ending X and Y to get 4 corners of the rectangle:
canvas.drawRect(startX, startY, x, y, paint3);
You can find more info about drawRect here and here.
EDIT i dont have nice environment to test it... but I found some errors in your answer:
else if (currentDrawMode == DrawMode.RectDrawMode) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
paint3.setColor(Color.RED);
startX = event.getX();
startY = event.getY();
break;
// return true;
case MotionEvent.ACTION_MOVE:
endX = event.getX();
endY = event.getY();
canvas.drawRect(startX, startY, endX, endY, paint3);
//invalidate(); // Tell View that the canvas needs to be redrawn
break;
case MotionEvent.ACTION_UP:
paint3.setColor(Color.BLACK);
canvas.drawRect(startX, startY, endX, endY, paint3);
break;
default:
return false;
}
PS: if you want add info to your question edit it, but dont post new answers!!!
So I would like to implement an undo button for a drawing app I am working on for android. My current idea is to put the current draw path into a list of paths on MotionEvent.ACTION_UP. In onDraw(), simply draw everything that is in the list of paths. When the user presses undo, you remove the last drawn path from the list of paths and call invalidate(), forcing onDraw() to be called, which will draw everything in the list of paths. Since we removed the previous path from the list, that path should not be drawn and therefore should be "undone".
The problem I am having is the path never seems to actually be undone. It seems like the logic in my head is correct, but this implementation does not work correctly. Any help will be greatly appreciated.
Here is my code:
DrawingView.java:
Instance variables (to clarify following methods):
private Context context;
private Path drawPath;
private Paint drawPaint;
private Paint canvasPaint;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private int previousPaintColor;
private int paintColor;
private float brushSize;
private float eraserSize;
private float lastBrushSize;
private boolean isErasing = false;
private List<Path> moveList = null;
private List<Path> undoList = null;
private List<Path> currentMoveList = null;
Called from constructor:
private void setupDrawing() {
drawPath = new Path();
drawPaint = new Paint();
brushSize = getResources().getInteger(R.integer.default_brush_size);
lastBrushSize = brushSize;
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
onDraw:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
for (Path path : currentMoveList) {
canvas.drawPath(path, drawPaint);
}
for (Path path : moveList) {
canvas.drawPath(path, drawPaint);
}
}
onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
currentMoveList.add(drawPath);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
moveList.add(drawPath);
drawPath = new Path();
currentMoveList.clear();
break;
default:
return false;
}
invalidate();
return true;
}
undo() :
public void undo() {
if (moveList.size() > 0) {
undoList.add(moveList.remove(moveList.size() - 1));
invalidate();
}
}
Regards,
Kyle.
After some trial and error, here is the fix:
Remove this line of code:
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
from onDraw() method and everything works perfectly :)
**Undo your onDraw override method ((TESTED)) **
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
for (Path path : currentMoveList) {
canvas.drawPath(path, drawPaint);
}
for (Path path : moveList) {
canvas.drawPath(path, drawPaint);
}
super.onDraw(canvas);
}
I'm building a crossword game. Ideally, I would like to use similar functionality like the code below. However, I just need help figuring out how to draw a textfield on the screen so the user can input his/her words. Here's the code I have so far. How would I modify this code to draw a textfield instead of paint? Appreciate any help.
public class Word extends View {
private Paint paint = new Paint();
private Path path = new Path();
public Word(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(30f);
paint.setColor(Color.GRAY);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
break;
default:
return false;
}
// Schedules a repaint.
invalidate();
return true;
}
Use a TextView in addition to your "Word" view, and set its visibility to GONE, until you need it to show, then set it to VISIBLE when you want it.