I have a desktop based UI application written in Java SWT running on windows.
I want to add a button on the UI screen whose behaviour should be similar to the badges on an iphone or facebook notifications as shown in the images below.
The number on the badge will be dynamic and will increase or decrease based on the number of pending notifications.
How can I implement something similar in SWT/AWT?
IOS Badge:
Facebook Notification:
I've implemented something like that recently. You can simply paint a custom image with GC, and the overlay on your desired icon.
I'm including my helper class here. It's not the cleanest code (a lot of stuff is hardcoded), but you'll get the point. The notification bubble resizes itself depending on the number of notifications (max 999).
How to use (Remember to cache and/or dispose your images!):
Image decoratedIcon = new ImageOverlayer()
.baseImage(baseImage) // You icon/badget
.overlayImage(ImageOverlayer.createNotifImage(5)) // 5 notifications
.overlayImagePosition(OverlayedImagePosition.TOP_RIGHT)
.createImage();
/**
* <pre>
* The difference between this and the ImageBuilder is
* that ImageOverlayer does not chain the images, rather
* just overlays them one onto another.
*
*
* Rules:
*
* 1.) Images are not disposed. Resource handing must be done externally.
* 2.) Only two images allowed, for now.
* 3.) The size of the composite image should normally be the size of the
* base image, BUT: if the overlaying image is larger, then larger
* parameters are grabbed, and the base image is still underneath.
* 4.) Use the builder APIs to set the base and overlaying images. The
* position of the overlaying image is optional, and CENTER by default.
* When you've set these, simply call createImage()
*
* Further improvements:
*
* - Combine this with ImageBuilder. These two composers should be welded.
*
* </pre>
*
* #author grec.georgian#gmail.com
*
*/
public class ImageOverlayer extends CompositeImageDescriptor
{
// ==================== 1. Static Fields ========================
public enum OverlayedImagePosition
{
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER;
}
// ====================== 2. Instance Fields =============================
private ImageData baseImageData;
private ImageData overlayedImageData;
private OverlayedImagePosition overlayedImagePosition = OverlayedImagePosition.CENTER;
// ==================== 3. Static Methods ====================
/**
* Creates a red circle with a white bold number inside it.
* Does not cache the final image.
*/
public static final Image createNotifImage(final int numberOfNotifications)
{
// Initial width and height - hardcoded for now
final int width = 14;
int height = 14;
// Initial font size
int fontSize = 100;
int decorationWidth = width;
String textToDraw = String.valueOf(numberOfNotifications);
final int numberLength = Integer.toString(numberOfNotifications).length();
if(numberLength > 3)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
// - smaller font size so the new text will fit(set to 999+) if we have
// a number of notifications with more than 3 digits
decorationWidth += numberLength * 2;
height -= 4;
fontSize = 80;
textToDraw = "999+"; //$NON-NLS-1$
}
else if (numberLength > 2)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
decorationWidth += numberLength * 1.5;
height -= 4;
}
final Font font = new Font(Display.getDefault(), "Arial", width / 2, SWT.BOLD); //$NON-NLS-1$
final Image canvas = new Image(null, decorationWidth, height);
final GC gc = new GC(canvas);
gc.setAntialias(SWT.ON);
gc.setAlpha(0);
gc.fillRectangle(0, 0, decorationWidth, height);
gc.setAlpha(255);
gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
// spetrila, 2014.12.17: In case we have more than two digits in the number of notifications,
// we will change the decoration to a rounded rectangle so it can contain
// all of the digits in the notification number
if(decorationWidth == width)
gc.fillOval(0, 0, decorationWidth - 1, height - 1);
else
gc.fillRoundRectangle(0, 0, decorationWidth, height, 10, 10);
final FontData fontData = font.getFontData()[0];
fontData.setHeight((int) (fontData.getHeight() * fontSize / 100.0 + 0.5));
fontData.setStyle(SWT.BOLD);
final Font newFont = new Font(Display.getCurrent(), fontData);
// gc.setFont(AEFUIActivator.getDefault().getCustomizedFont(font, fontSize, SWT.BOLD));
gc.setFont(newFont);
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
final Point textSize = gc.stringExtent(textToDraw);
final int xPos = (decorationWidth - textSize.x) / 2;
final int yPos = (height - textSize.y) / 2;
gc.drawText(textToDraw, xPos + 1, yPos, true);
gc.dispose();
final ImageData imgData = canvas.getImageData();
// Remove white transparent pixels
final int whitePixel = imgData.palette.getPixel(new RGB(255,255,255));
imgData.transparentPixel = whitePixel;
final Image finalImage = new Image(null, imgData);
canvas.dispose();
font.dispose();
newFont.dispose();
return finalImage;
}
// ==================== 5. Creators ====================
#Override
public Image createImage()
{
if (baseImageData == null || overlayedImageData == null)
throw new IllegalArgumentException("Please check the ImageOverlayer. One of the overlaying images is NULL."); //$NON-NLS-1$
return super.createImage();
}
// ==================== 6. Action Methods ====================
#Override
protected void drawCompositeImage(final int width, final int height)
{
/*
* These two determine where the overlayed image top left
* corner should go, relative to the base image behind it.
*/
int xPos = 0;
int yPos = 0;
switch (overlayedImagePosition)
{
case TOP_LEFT:
break;
case TOP_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
break;
case BOTTOM_LEFT:
yPos = baseImageData.height - overlayedImageData.height;
break;
case BOTTOM_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
yPos = baseImageData.height - overlayedImageData.height;
break;
case CENTER:
xPos = (baseImageData.width - overlayedImageData.width) / 2;
yPos = (baseImageData.height - overlayedImageData.height) / 2;
break;
default:
break;
}
drawImage(baseImageData, 0, 0);
drawImage(overlayedImageData, xPos, yPos);
}
// ==================== 7. Getters & Setters ====================
final public ImageOverlayer overlayImagePosition(final OverlayedImagePosition overlayImagePosition)
{
this.overlayedImagePosition = overlayImagePosition;
return this;
}
final public ImageOverlayer baseImage(final ImageData baseImageData)
{
this.baseImageData = baseImageData;
return this;
}
final public ImageOverlayer baseImage(final Image baseImage)
{
this.baseImageData = baseImage.getImageData();
return this;
}
final public ImageOverlayer baseImage(final ImageDescriptor baseImageDescriptor)
{
this.baseImageData = baseImageDescriptor.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageData overlayImageData)
{
this.overlayedImageData = overlayImageData;
return this;
}
final public ImageOverlayer overlayImage(final Image overlayImage)
{
this.overlayedImageData = overlayImage.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageDescriptor overlayImageDescriptor)
{
this.overlayedImageData = overlayImageDescriptor.getImageData();
return this;
}
#Override
protected Point getSize()
{
// The size of the composite image is determined by the maximum size between the two building images,
// although keep in mind that the base image always comes underneath the overlaying one.
return new Point( max(baseImageData.width, overlayedImageData.width), max(baseImageData.height, overlayedImageData.height) );
}
}
Also you can use control decorations for this. The advantage is you can easily hide/show the notification with hide() and show() methods and add tool-tip text and listeners to it.
Check this blog for how to use control decoration. Use Button widget instead of Text for your case.
Create the notification image as shown below and set it to ControlDecoration object.
Image image = new Image(display, 20, 25);
GC gc = new GC(image);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, 20, 25);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
int notif = 5;
gc.drawText(new Integer(notif).toString(), 5, 5);
gc.dispose();
Related
I want to print a table with Java but my class that implements the Printable class print one page and when my table is big, break it into two pages and just prints the last page. I won't print one page if there is more pages print all of them.
this is my TablePrintable Class:
class TablePrintable implements Printable {
/** The table to print. */
private JTable table;
/** For quick reference to the table's header. */
private JTableHeader header;
/** For quick reference to the table's column model. */
private TableColumnModel colModel;
/** To save multiple calculations of total column width. */
private int totalColWidth;
/** The printing mode of this printable. */
private JTable.PrintMode printMode;
/** Provides the header text for the table. */
private MessageFormat headerFormat;
/** Provides the footer text for the table. */
private MessageFormat footerFormat;
/** The most recent page index asked to print. */
private int last = -1;
/** The next row to print. */
private int row = 0;
/** The next column to print. */
private int col = 0;
/** Used to store an area of the table to be printed. */
private final Rectangle clip = new Rectangle(0, 0, 0, 0);
/** Used to store an area of the table's header to be printed. */
private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
/** Saves the creation of multiple rectangles. */
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
/** Vertical space to leave between table and header/footer text. */
private static final int H_F_SPACE = 15;
/** Font size for the header text. */
private static final float HEADER_FONT_SIZE = 14.0f;
/** Font size for the footer text. */
private static final float FOOTER_FONT_SIZE = 10.0f;
/** The font to use in rendering header text. */
private Font headerFont;
/** The font to use in rendering footer text. */
private Font footerFont;
/**
* Create a new <code>TablePrintable</code> for the given
* <code>JTable</code>. Header and footer text can be specified using the
* two <code>MessageFormat</code> parameters. When called upon to provide a
* String, each format is given the current page number.
*
* #param table
* the table to print
* #param printMode
* the printing mode for this printable
* #param headerFormat
* a <code>MessageFormat</code> specifying the text to be used in
* printing a header, or null for none
* #param footerFormat
* a <code>MessageFormat</code> specifying the text to be used in
* printing a footer, or null for none
* #throws IllegalArgumentException
* if passed an invalid print mode
*/
public TablePrintable(JTable table, JTable.PrintMode printMode,
MessageFormat headerFormat,
MessageFormat footerFormat) {
this.table = table;
header = table.getTableHeader();
colModel = table.getColumnModel();
totalColWidth = colModel.getTotalColumnWidth();
if (header != null) {
// the header clip height can be set once since it's unchanging
hclip.height = header.getHeight();
}
this.printMode = printMode;
this.headerFormat = headerFormat;
this.footerFormat = footerFormat;
// derive the header and footer font from the table's font
headerFont = table.getFont().deriveFont(Font.BOLD, HEADER_FONT_SIZE);
footerFont = table.getFont().deriveFont(Font.PLAIN, FOOTER_FONT_SIZE);
}
/**
* Prints the specified page of the table into the given {#link Graphics}
* context, in the specified format.
*
* #param graphics
* the context into which the page is drawn
* #param pageFormat
* the size and orientation of the page being drawn
* #param pageIndex
* the zero based index of the page to be drawn
* #return PAGE_EXISTS if the page is rendered successfully, or NO_SUCH_PAGE
* if a non-existent page index is specified
* #throws PrinterException
* if an error causes printing to be aborted
*/
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
// for easy access to these values
final int imgWidth = (int) pageFormat.getImageableWidth();
final int imgHeight = (int) pageFormat.getImageableHeight();
if (imgWidth <= 0) {
throw new PrinterException("Width of printable area is too small.");
}
if((table.getRowCount() * table.getRowHeight()) > imgHeight){
pageIndex++;
}
// to pass the page number when formatting the header and footer text
Object[] pageNumber = new Object[] { new Integer(pageIndex + 1) };
// fetch the formatted header text, if any
String headerText = null;
if (headerFormat != null) {
headerText = headerFormat.format(pageNumber);
}
// fetch the formatted footer text, if any
String footerText = null;
if (footerFormat != null) {
footerText = footerFormat.format(pageNumber);
}
// to store the bounds of the header and footer text
Rectangle2D hRect = null;
Rectangle2D fRect = null;
// the amount of vertical space needed for the header and footer text
int headerTextSpace = 0;
int footerTextSpace = 0;
// the amount of vertical space available for printing the table
int availableSpace = imgHeight;
System.out.println("available space: " + availableSpace);
System.out.println("the avail space // row Height => " +
(availableSpace/table.getRowHeight()));
// if there's header text, find out how much space is needed for it
// and subtract that from the available space
if (headerText != null) {
graphics.setFont(headerFont);
hRect = graphics.getFontMetrics().getStringBounds(headerText,
graphics);
headerTextSpace = (int) Math.ceil(hRect.getHeight());
availableSpace -= headerTextSpace + H_F_SPACE;
}
// if there's footer text, find out how much space is needed for it
// and subtract that from the available space
if (footerText != null) {
graphics.setFont(footerFont);
fRect = graphics.getFontMetrics().getStringBounds(footerText,
graphics);
footerTextSpace = (int) Math.ceil(fRect.getHeight());
availableSpace -= footerTextSpace;// TODO
}
if (availableSpace <= 0) {
throw new PrinterException("Height of printable area is too
small.");
}
// depending on the print mode, we may need a scale factor to
// fit the table's entire width on the page
double sf = 1.0D;
if (printMode == JTable.PrintMode.FIT_WIDTH && totalColWidth > imgWidth)
{
// if not, we would have thrown an acception previously
assert imgWidth > 0;
// it must be, according to the if-condition, since imgWidth > 0
assert totalColWidth > 1;
sf = (double) imgWidth / (double) totalColWidth;
}
// dictated by the previous two assertions
assert sf > 0;
// This is in a loop for two reasons:
// First, it allows us to catch up in case we're called starting
// with a non-zero pageIndex. Second, we know that we can be called
// for the same page multiple times. The condition of this while
// loop acts as a check, ensuring that we don't attempt to do the
// calculations again when we are called subsequent times for the
// same page.
while (last < pageIndex) {
// if we are finished all columns in all rows
if (row >= table.getRowCount() && col == 0) {
return NO_SUCH_PAGE;
}
// rather than multiplying every row and column by the scale factor
// in findNextClip, just pass a width and height that have already
// been divided by it
int scaledWidth = (int) (imgWidth / sf);
int scaledHeight = (int) ((availableSpace - hclip.height) / sf);
// calculate the area of the table to be printed for this page
findNextClip(scaledWidth, scaledHeight);
last++;
}
// translate into the co-ordinate system of the pageFormat
Graphics2D g2d = (Graphics2D) graphics;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// to save and store the transform
AffineTransform oldTrans;
// if there's footer text, print it at the bottom of the imageable area
if (footerText != null) {
oldTrans = g2d.getTransform();
g2d.translate(0, imgHeight - footerTextSpace);
printFooterText(g2d, footerText, fRect, footerFont, imgWidth);
g2d.setTransform(oldTrans);
}
// if there's header text, print it at the top of the imageable area
// and then translate downwards
if (headerText != null) {
printHeaderText(g2d, headerText, hRect, headerFont, imgWidth);
g2d.translate(0, headerTextSpace + H_F_SPACE);
}
// constrain the table output to the available space
tempRect.x = 0;
tempRect.y = 0;
tempRect.width = imgWidth;
tempRect.height = availableSpace;
g2d.clip(tempRect);
// if we have a scale factor, scale the graphics object to fit
// the entire width
if (sf != 1.0D) {
g2d.scale(sf, sf);
// otherwise, ensure that the current portion of the table is
// centered horizontally
} else {
int diff = (imgWidth - clip.width) / 2;
g2d.translate(diff, 0);
}
// store the old transform and clip for later restoration
oldTrans = g2d.getTransform();
Shape oldClip = g2d.getClip();
// if there's a table header, print the current section and
// then translate downwards
if (header != null) {
hclip.x = clip.x;
hclip.width = clip.width;
g2d.translate(-hclip.x, 0);
g2d.clip(hclip);
header.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// translate downwards
g2d.translate(0, hclip.height);
}
// print the current section of the table
g2d.translate(-clip.x, -clip.y);
g2d.clip(clip);
table.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
return PAGE_EXISTS;
}
/**
* A helper method that encapsulates common code for rendering the header
* and footer text.
*
* #param g2d
* the graphics to draw into
* #param text
* the text to draw, non null
* #param rect
* the bounding rectangle for this text, as calculated at the
* given font, non null
* #param font
* the font to draw the text in, non null
* #param imgWidth
* the width of the area to draw into
*/
private void printHeaderText(Graphics2D g2d, String text, Rectangle2D rect,
Font font, int imgWidth) {
int tx;// TODO
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int) ((imgWidth - rect.getWidth()) / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int) (Math.ceil(rect.getWidth()) - imgWidth);
}
int ty = (int) Math.ceil(Math.abs(rect.getY()));
g2d.setColor(Color.BLACK);
g2d.setFont(MainUi.fLiner);
g2d.drawString(text, tx, ty);
}
private void printFooterText(Graphics2D g2d, String text, Rectangle2D rect,
Font font, int imgWidth) {
int tx;// TODO
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int) ((imgWidth - rect.getWidth()) / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int) (Math.ceil(rect.getWidth()) - imgWidth);
}
// int ty = (int) Math.ceil(Math.abs(rect.getY() + 30));
int ty = (int) Math.ceil(rect.getHeight());
g2d.setColor(Color.BLACK);
g2d.setFont(MainUi.ffLiner);
g2d.drawString(text, tx, ty-3);
}
/**
* Calculate the area of the table to be printed for the next page. This
* should only be called if there are rows and columns left to print.
*
* To avoid an infinite loop in printing, this will always put at least one
* cell on each page.
*
* #param pw
* the width of the area to print in
* #param ph
* the height of the area to print in
*/
private void findNextClip(int pw, int ph) {
final boolean ltr = table.getComponentOrientation().isLeftToRight();
// if we're ready to start a new set of rows
if (col == 0) {
if (ltr) {
// adjust clip to the left of the first column
clip.x = 0;
} else {
// adjust clip to the right of the first column
clip.x = totalColWidth;
}
// adjust clip to the top of the next set of rows
clip.y += clip.height;
// adjust clip width and height to be zero
clip.width = 0;
clip.height = 0;
// fit as many rows as possible, and at least one
int rowCount = table.getRowCount();
int rowHeight = table.getRowHeight(row);
do {
clip.height += rowHeight;
if (++row >= rowCount) {
break;
}
rowHeight = table.getRowHeight(row);
} while (clip.height + rowHeight <= ph);
}
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since
// we'll always fit all columns on the page
if (printMode == JTable.PrintMode.FIT_WIDTH) {
clip.x = 0;
clip.width = totalColWidth;
return;
}
if (ltr) {
// adjust clip to the left of the next set of columns
clip.x += clip.width;
}
// adjust clip width to be zero
clip.width = 0;
// fit as many columns as possible, and at least one
int colCount = table.getColumnCount();
int colWidth = colModel.getColumn(col).getWidth();
do {
clip.width += colWidth;
if (!ltr) {
clip.x -= colWidth;
}
if (++col >= colCount) {
// reset col to 0 to indicate we're finished all columns
col = 0;
break;
}
colWidth = colModel.getColumn(col).getWidth();
} while (clip.width + colWidth <= pw);
}
}
I was looking Cropper and image crooper open source App by Edmodo, it does a pretty good job except instead of Image cropping I want to use it to scale my images, my Logic Was everytime the cropper Window/View changes in size I would change the height and width of my imageview to match the Crop view but everytime I do this, nothing happens, but looking at LogCat it seems my height and width do change except the imageview still looks the same even after changes below is the Class, my changes are commented in the onSizeChanged()
/*
* Copyright 2013, Edmodo, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License.
* You may obtain a copy of the License in the LICENSE file, or at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.theartofdev.edmodo.cropper.cropwindow;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import com.theartofdev.edmodo.cropper.CropImageView;
import com.theartofdev.edmodo.cropper.cropwindow.edge.Edge;
import com.theartofdev.edmodo.cropper.cropwindow.handle.Handle;
import com.theartofdev.edmodo.cropper.util.AspectRatioUtil;
import com.theartofdev.edmodo.cropper.util.HandleUtil;
import com.theartofdev.edmodo.cropper.util.PaintUtil;
/**
* A custom View representing the crop window and the shaded background outside the crop window.
*/
public class CropOverlayView extends View {
//region: Fields and Consts
private static final int SNAP_RADIUS_DP = 6;
private static final float DEFAULT_SHOW_GUIDELINES_LIMIT = 100;
// Gets default values from PaintUtil, sets a bunch of values such that the
// corners will draw correctly
private static final float DEFAULT_CORNER_THICKNESS_DP = PaintUtil.getCornerThickness();
private static final float DEFAULT_LINE_THICKNESS_DP = PaintUtil.getLineThickness();
private static final float DEFAULT_CORNER_OFFSET_DP = (DEFAULT_CORNER_THICKNESS_DP / 2) - (DEFAULT_LINE_THICKNESS_DP / 2);
private static final float DEFAULT_CORNER_EXTENSION_DP = DEFAULT_CORNER_THICKNESS_DP / 2
+ DEFAULT_CORNER_OFFSET_DP;
private static final float DEFAULT_CORNER_LENGTH_DP = 20;
private static final int GUIDELINES_ON_TOUCH = 1;
private static final int GUIDELINES_ON = 2;
private static RectF mRectF = new RectF();
/**
* The Paint used to draw the white rectangle around the crop area.
*/
private Paint mBorderPaint;
/**
* The Paint used to draw the guidelines within the crop area when pressed.
*/
private Paint mGuidelinePaint;
/**
* The Paint used to draw the corners of the Border
*/
private Paint mCornerPaint;
/**
* The Paint used to darken the surrounding areas outside the crop area.
*/
private Paint mBackgroundPaint;
/**
* The bounding box around the Bitmap that we are cropping.
*/
private Rect mBitmapRect;
// The radius of the touch zone (in pixels) around a given Handle.
private float mHandleRadius;
// An edge of the crop window will snap to the corresponding edge of a
// specified bounding box when the crop window edge is less than or equal to
// this distance (in pixels) away from the bounding box edge.
private float mSnapRadius;
// Holds the x and y offset between the exact touch location and the exact
// handle location that is activated. There may be an offset because we
// allow for some leeway (specified by mHandleRadius) in activating a
// handle. However, we want to maintain these offset values while the handle
// is being dragged so that the handle doesn't jump.
private Pair<Float, Float> mTouchOffset;
// The Handle that is currently pressed; null if no Handle is pressed.
private Handle mPressedHandle;
// Flag indicating if the crop area should always be a certain aspect ratio
// (indicated by mTargetAspectRatio).
private boolean mFixAspectRatio = CropImageView.DEFAULT_FIXED_ASPECT_RATIO;
// Floats to save the current aspect ratio of the image
private int mAspectRatioX = CropImageView.DEFAULT_ASPECT_RATIO_X;
private int mAspectRatioY = CropImageView.DEFAULT_ASPECT_RATIO_Y;
// The aspect ratio that the crop area should maintain; this variable is
// only used when mMaintainAspectRatio is true.
private float mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
/**
* Instance variables for customizable attributes
*/
private int mGuidelines;
/**
* The shape of the cropping area - rectangle/circular.
*/
private CropImageView.CropShape mCropShape;
// Whether the Crop View has been initialized for the first time
private boolean initializedCropWindow = false;
// Instance variables for the corner values
private float mCornerExtension;
private float mCornerOffset;
private float mCornerLength;
//endregion
public CropOverlayView(Context context) {
super(context);
init(context);
}
public CropOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Informs the CropOverlayView of the image's position relative to the
* ImageView. This is necessary to call in order to draw the crop window.
*
* #param bitmapRect the image's bounding box
*/
public void setBitmapRect(Rect bitmapRect) {
mBitmapRect = bitmapRect;
initCropWindow(mBitmapRect);
}
/**
* Resets the crop overlay view.
*/
public void resetCropOverlayView() {
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
/**
* The shape of the cropping area - rectangle/circular.
*/
public void setCropShape(CropImageView.CropShape cropShape) {
mCropShape = cropShape;
invalidate();
}
/**
* Sets the guidelines for the CropOverlayView to be either on, off, or to
* show when resizing the application.
*
* #param guidelines Integer that signals whether the guidelines should be
* on, off, or only showing when resizing.
*/
public void setGuidelines(int guidelines) {
if (guidelines < 0 || guidelines > 2)
throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");
else {
mGuidelines = guidelines;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets whether the aspect ratio is fixed or not; true fixes the aspect
* ratio, while false allows it to be changed.
*
* #param fixAspectRatio Boolean that signals whether the aspect ratio
* should be maintained.
*/
public void setFixedAspectRatio(boolean fixAspectRatio) {
mFixAspectRatio = fixAspectRatio;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
/**
* Sets the X value of the aspect ratio; is defaulted to 1.
*
* #param aspectRatioX int that specifies the new X value of the aspect
* ratio
*/
public void setAspectRatioX(int aspectRatioX) {
if (aspectRatioX <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioX = aspectRatioX;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets the Y value of the aspect ratio; is defaulted to 1.
*
* #param aspectRatioY int that specifies the new Y value of the aspect
* ratio
*/
public void setAspectRatioY(int aspectRatioY) {
if (aspectRatioY <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioY = aspectRatioY;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets all initial values, but does not call initCropWindow to reset the
* views. Used once at the very start to initialize the attributes.
*
* #param guidelines Integer that signals whether the guidelines should be
* on, off, or only showing when resizing.
* #param fixAspectRatio Boolean that signals whether the aspect ratio
* should be maintained.
* #param aspectRatioX float that specifies the new X value of the aspect
* ratio
* #param aspectRatioY float that specifies the new Y value of the aspect
* ratio
*/
public void setInitialAttributeValues(int guidelines, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY) {
if (guidelines < 0 || guidelines > 2)
throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");
else
mGuidelines = guidelines;
mFixAspectRatio = fixAspectRatio;
if (aspectRatioX <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioX = aspectRatioX;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
}
if (aspectRatioY <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioY = aspectRatioY;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
}
}
//region: Private methods
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Initialize the crop window here because we need the size of the view
// to have been determined.
//my changes to update imageview height and width
CropImageView.mImageView.getLayoutParams().height = h;
CropImageView.mImageView.getLayoutParams().width = w;
CropImageView.mImageView.requestLayout();
CropImageView.mImageView.invalidate();
initCropWindow(mBitmapRect);
// CropImageView.mImageView.invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw translucent background for the cropped area.
drawBackground(canvas, mBitmapRect);
if (showGuidelines()) {
// Determines whether guidelines should be drawn or not
if (mGuidelines == GUIDELINES_ON) {
drawRuleOfThirdsGuidelines(canvas);
} else if (mGuidelines == GUIDELINES_ON_TOUCH) {
// Draw only when resizing
if (mPressedHandle != null)
drawRuleOfThirdsGuidelines(canvas);
}
}
float w = mBorderPaint.getStrokeWidth();
float l = Edge.LEFT.getCoordinate() + w;
float t = Edge.TOP.getCoordinate() + w;
float r = Edge.RIGHT.getCoordinate() - w;
float b = Edge.BOTTOM.getCoordinate() - w;
if (mCropShape == CropImageView.CropShape.RECTANGLE) {
// Draw rectangle crop window border.
canvas.drawRect(l, t, r, b, mBorderPaint);
drawCorners(canvas);
} else {
// Draw circular crop window border
mRectF.set(l, t, r, b);
canvas.drawOval(mRectF, mBorderPaint);
}
}
#Override
public boolean onTouchEvent(#SuppressWarnings("NullableProblems") MotionEvent event) {
// If this View is not enabled, don't allow for touch interactions.
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onActionDown(event.getX(), event.getY());
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
onActionUp();
return true;
case MotionEvent.ACTION_MOVE:
onActionMove(event.getX(), event.getY());
getParent().requestDisallowInterceptTouchEvent(true);
return true;
default:
return false;
}
}
private void init(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
mHandleRadius = HandleUtil.getTargetRadius(context);
mSnapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SNAP_RADIUS_DP,
displayMetrics);
mBorderPaint = PaintUtil.newBorderPaint(context);
mGuidelinePaint = PaintUtil.newGuidelinePaint();
mBackgroundPaint = PaintUtil.newBackgroundPaint(context);
mCornerPaint = PaintUtil.newCornerPaint(context);
// Sets the values for the corner sizes
mCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_OFFSET_DP,
displayMetrics);
mCornerExtension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_EXTENSION_DP,
displayMetrics);
mCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_LENGTH_DP,
displayMetrics);
// Sets guidelines to default until specified otherwise
mGuidelines = CropImageView.DEFAULT_GUIDELINES;
}
/**
* Set the initial crop window size and position. This is dependent on the
* size and position of the image being cropped.
*
* #param bitmapRect the bounding box around the image being cropped
*/
private void initCropWindow(Rect bitmapRect) {
if (bitmapRect.width() == 0 || bitmapRect.height() == 0) {
return;
}
// Tells the attribute functions the crop window has already been
// initialized
if (!initializedCropWindow) {
initializedCropWindow = true;
}
if (mFixAspectRatio
&& (bitmapRect.left != 0 || bitmapRect.right != 0
|| bitmapRect.top != 0 || bitmapRect.bottom != 0)) {
// If the image aspect ratio is wider than the crop aspect ratio,
// then the image height is the determining initial length. Else,
// vice-versa.
if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > mTargetAspectRatio) {
Edge.TOP.setCoordinate(bitmapRect.top);
Edge.BOTTOM.setCoordinate(bitmapRect.bottom);
final float centerX = getWidth() / 2f;
//dirty fix for wrong crop overlay aspect ratio when using fixed aspect ratio
mTargetAspectRatio = (float) mAspectRatioX / mAspectRatioY;
// Limits the aspect ratio to no less than 40 wide or 40 tall
final float cropWidth = Math.max(Edge.MIN_CROP_LENGTH_PX,
AspectRatioUtil.calculateWidth(Edge.TOP.getCoordinate(),
Edge.BOTTOM.getCoordinate(),
mTargetAspectRatio));
// Create new TargetAspectRatio if the original one does not fit
// the screen
if (cropWidth == Edge.MIN_CROP_LENGTH_PX) {
mTargetAspectRatio = (Edge.MIN_CROP_LENGTH_PX) / (Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate());
}
final float halfCropWidth = cropWidth / 2f;
Edge.LEFT.setCoordinate(centerX - halfCropWidth);
Edge.RIGHT.setCoordinate(centerX + halfCropWidth);
} else {
Edge.LEFT.setCoordinate(bitmapRect.left);
Edge.RIGHT.setCoordinate(bitmapRect.right);
final float centerY = getHeight() / 2f;
// Limits the aspect ratio to no less than 40 wide or 40 tall
final float cropHeight = Math.max(Edge.MIN_CROP_LENGTH_PX,
AspectRatioUtil.calculateHeight(Edge.LEFT.getCoordinate(),
Edge.RIGHT.getCoordinate(),
mTargetAspectRatio));
// Create new TargetAspectRatio if the original one does not fit
// the screen
if (cropHeight == Edge.MIN_CROP_LENGTH_PX) {
mTargetAspectRatio = (Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate()) / Edge.MIN_CROP_LENGTH_PX;
}
final float halfCropHeight = cropHeight / 2f;
Edge.TOP.setCoordinate(centerY - halfCropHeight);
Edge.BOTTOM.setCoordinate(centerY + halfCropHeight);
}
} else { // ... do not fix aspect ratio...
// Initialize crop window to have 10% padding w/ respect to image.
final float horizontalPadding = 0.1f * bitmapRect.width();
final float verticalPadding = 0.1f * bitmapRect.height();
Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding);
Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding);
Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding);
Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding);
}
}
/**
* Indicates whether the crop window is small enough that the guidelines
* should be shown. Public because this function is also used to determine
* if the center handle should be focused.
*
* #return boolean Whether the guidelines should be shown or not
*/
public static boolean showGuidelines() {
if ((Math.abs(Edge.LEFT.getCoordinate() - Edge.RIGHT.getCoordinate()) < DEFAULT_SHOW_GUIDELINES_LIMIT)
|| (Math.abs(Edge.TOP.getCoordinate() - Edge.BOTTOM.getCoordinate()) < DEFAULT_SHOW_GUIDELINES_LIMIT)) {
return false;
} else {
return true;
}
}
private void drawRuleOfThirdsGuidelines(Canvas canvas) {
float w = mBorderPaint.getStrokeWidth();
float l = Edge.LEFT.getCoordinate() + w;
float t = Edge.TOP.getCoordinate() + w;
float r = Edge.RIGHT.getCoordinate() - w;
float b = Edge.BOTTOM.getCoordinate() - w;
if (mCropShape == CropImageView.CropShape.OVAL) {
l += 15 * mGuidelinePaint.getStrokeWidth();
t += 15 * mGuidelinePaint.getStrokeWidth();
r -= 15 * mGuidelinePaint.getStrokeWidth();
b -= 15 * mGuidelinePaint.getStrokeWidth();
}
// Draw vertical guidelines.
final float oneThirdCropWidth = Edge.getWidth() / 3;
final float x1 = l + oneThirdCropWidth;
canvas.drawLine(x1, t, x1, b, mGuidelinePaint);
final float x2 = r - oneThirdCropWidth;
canvas.drawLine(x2, t, x2, b, mGuidelinePaint);
// Draw horizontal guidelines.
final float oneThirdCropHeight = Edge.getHeight() / 3;
final float y1 = t + oneThirdCropHeight;
canvas.drawLine(l, y1, r, y1, mGuidelinePaint);
final float y2 = b - oneThirdCropHeight;
canvas.drawLine(l, y2, r, y2, mGuidelinePaint);
}
private void drawBackground(Canvas canvas, Rect bitmapRect) {
final float l = Edge.LEFT.getCoordinate();
final float t = Edge.TOP.getCoordinate();
final float r = Edge.RIGHT.getCoordinate();
final float b = Edge.BOTTOM.getCoordinate();
if (mCropShape == CropImageView.CropShape.RECTANGLE) {
canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, t, mBackgroundPaint);
canvas.drawRect(bitmapRect.left, b, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);
canvas.drawRect(bitmapRect.left, t, l, b, mBackgroundPaint);
canvas.drawRect(r, t, bitmapRect.right, b, mBackgroundPaint);
} else {
Path circleSelectionPath = new Path();
mRectF.set(l, t, r, b);
circleSelectionPath.addOval(mRectF, Path.Direction.CW);
canvas.clipPath(circleSelectionPath, Region.Op.XOR);
canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);
canvas.restore();
}
}
private void drawCorners(Canvas canvas) {
float w = mBorderPaint.getStrokeWidth();
final float l = Edge.LEFT.getCoordinate() + w;
final float t = Edge.TOP.getCoordinate() + w;
final float r = Edge.RIGHT.getCoordinate() - w;
final float b = Edge.BOTTOM.getCoordinate() - w;
// Top left
canvas.drawLine(l - mCornerOffset, t - mCornerExtension, l - mCornerOffset, t + mCornerLength, mCornerPaint);
canvas.drawLine(l, t - mCornerOffset, l + mCornerLength, t - mCornerOffset, mCornerPaint);
// Top right
canvas.drawLine(r + mCornerOffset, t - mCornerExtension, r + mCornerOffset, t + mCornerLength, mCornerPaint);
canvas.drawLine(r, t - mCornerOffset, r - mCornerLength, t - mCornerOffset, mCornerPaint);
// Bottom left
canvas.drawLine(l - mCornerOffset, b + mCornerExtension, l - mCornerOffset, b - mCornerLength, mCornerPaint);
canvas.drawLine(l, b + mCornerOffset, l + mCornerLength, b + mCornerOffset, mCornerPaint);
// Bottom left
canvas.drawLine(r + mCornerOffset, b + mCornerExtension, r + mCornerOffset, b - mCornerLength, mCornerPaint);
canvas.drawLine(r, b + mCornerOffset, r - mCornerLength, b + mCornerOffset, mCornerPaint);
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_DOWN} event.
*
* #param x the x-coordinate of the down action
* #param y the y-coordinate of the down action
*/
private void onActionDown(float x, float y) {
final float left = Edge.LEFT.getCoordinate();
final float top = Edge.TOP.getCoordinate();
final float right = Edge.RIGHT.getCoordinate();
final float bottom = Edge.BOTTOM.getCoordinate();
mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);
if (mPressedHandle == null) {
return;
}
// Calculate the offset of the touch point from the precise location
// of the handle. Save these values in a member variable since we want
// to maintain this offset as we drag the handle.
mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom);
invalidate();
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_UP} or
* {#link android.view.MotionEvent#ACTION_CANCEL} event.
*/
private void onActionUp() {
if (mPressedHandle == null) {
return;
}
mPressedHandle = null;
invalidate();
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_MOVE} event.
*
* #param x the x-coordinate of the move event
* #param y the y-coordinate of the move event
*/
private void onActionMove(float x, float y) {
if (mPressedHandle == null) {
return;
}
// Adjust the coordinates for the finger position's offset (i.e. the
// distance from the initial touch to the precise handle location).
// We want to maintain the initial touch's distance to the pressed
// handle so that the crop window size does not "jump".
x += mTouchOffset.first;
y += mTouchOffset.second;
// Calculate the new crop window size/position.
if (mFixAspectRatio) {
mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius);
} else {
mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);
}
invalidate();
}
//endregion
}
You could just try using Picasso. It does all the scaling for you in the background after you put it into your Gradle build. Picasso
What I want is might be separated into several or one methods, whatever is best.
I have four values that are set (as fields):
Width of image the whole image (canvas so to speak)
Height of the whole image (height of the canvas)
Padding (how many pixels I want to have as padding)
How many images in i want in the width and height separately (can be separate values)
What I want is to have a whole image, that I'm putting small images inside (these are the bitmap sizes I want to get from the method).
I basically want to have the size of the images inside, both the width and height.
And coordinates in of the small images in the whole image view.
A small example might be:
The width of the image is 200 and I want a padding of 4, and I want to have 3 images on one row of the image.
The calculation would might look like this:
(200 - (4*(3+1))) / 3 = 61,33;
The 3+1 is only because I want to have padding on either side of the small images.
But this is only for width, i would like to have a universal solution that applies to height as well.
And calculate the height and width of every image that is inside the canvas-image.
And just to put some sugar on top.
I would like to have the x and y coordinates of every image that is inside.
How can this be done?
And how do I get every value?
The bitmaps (I'm developing android) is stored in an ArrayList.
Basically I want this, a basic grid layout:
http://developer.android.com/images/ui/gridview.png
The values I have based on this image:
I have the size of the light-blue area,
including the space between the dark-blue images (padding)
and how many dark blue spots I want in every
row and column.
What I want is the size (width and height of the dark-blue spots),
and the coordinates of those images (where they should be placed in the light-blue area, x and y coordinates).
Anybody have a good solution for this?
UPDATED THE TEXT FOR CLARITY
This is the code of what I'm doing and wanted to do.
If anyone has ideas how to improve the code I will change my accepted answer.
public class ClassName {
public static int bitmapSizeWidth;
public static int bitmapSizeHeight;
public static final int bitmapPadding = 8;
public static final int howManyImagesColumn = 3;
public static final int howManyImagesRows = 2;
public static Bitmap folderBitmap(ArrayList<Bitmap> bitmapArray, int imageViewWidth, int imageViewHeight) {
bitmapSizeWidth = imageViewWidth;
bitmapSizeHeight = imageViewHeight;
Bitmap b = Bitmap.createBitmap(bitmapSizeWidth, bitmapSizeHeight, Bitmap.Config.RGB_565);
Canvas c = new Canvas(b);
//Lets do it in a set coordinate system now
if (bitmapArray.size() >= 1)
c.drawBitmap(bitmapArray.get(0), bitmapPadding, bitmapPadding, paint);
if (bitmapArray.size() >= 2)
c.drawBitmap(bitmapArray.get(1), calculateSecondCoord().xPos, bitmapPadding, paint);
if (bitmapArray.size() >= 3)
c.drawBitmap(bitmapArray.get(2), calculateThirdCoord().xPos, bitmapPadding, paint);
if (bitmapArray.size() >= 4)
c.drawBitmap(bitmapArray.get(3), bitmapPadding, calculateSecondCoord().yPos, paint);
if (bitmapArray.size() >= 5) {
c.drawBitmap(bitmapArray.get(4), calculateSecondCoord().xPos, calculateSecondCoord().yPos, paint);
}
if (bitmapArray.size() >= 6) {
c.drawBitmap(bitmapArray.get(5), calculateThirdCoord().xPos, calculateSecondCoord().yPos, paint);
}
return b;
}
public static BitmapSize calculateSingleBitmapSize(int imageViewWidth, int imageViewHeight) {
bitmapSizeWidth = imageViewWidth;
bitmapSizeHeight = imageViewHeight;
BitmapSize bsize = new BitmapSize();
bsize.widthSize = (int) (bitmapSizeWidth -
(bitmapPadding * (howManyImagesColumn + 1))) / howManyImagesColumn;
bsize.heightSize = (int) (imageViewHeight - (bitmapPadding * (howManyImagesRows + 1))) / howManyImagesRows;
return bsize;
}
/*
* First coord = padding
* Second coord (bitmapPadding) + calculateSingleBitmapSize(bitmapSizeWidth);
* Third coord (bitmapPadding * 3) + (calculateSingleBitmapSize(bitmapSizeWidth) * 2)
* The math is supposed to be correct but can perhaps be done in a more efficient way
*/
private static BitmapCoord calculateSecondCoord() {
BitmapCoord bCoord = new BitmapCoord();
bCoord.xPos = (int) (bitmapPadding * (howManyImagesColumn - 1)) + calculateSingleBitmapSize(bitmapSizeWidth, bitmapSizeHeight).widthSize;
bCoord.yPos = (int) (bitmapPadding * (howManyImagesRows)) + calculateSingleBitmapSize(bitmapSizeWidth, bitmapSizeHeight).heightSize;
return bCoord;
}
private static BitmapCoord calculateThirdCoord() {
BitmapCoord bCoord = new BitmapCoord();
bCoord.xPos = (int) (bitmapPadding * howManyImagesColumn) + calculateSingleBitmapSize(bitmapSizeWidth, bitmapSizeHeight).widthSize * 2;
bCoord.yPos = (int) (bitmapPadding * howManyImagesRows - 1) + calculateSingleBitmapSize(bitmapSizeWidth, bitmapSizeHeight).heightSize * 2;
return bCoord;
}
public static class BitmapSize {
public int widthSize;
public int heightSize;
}
static class BitmapCoord {
int xPos;
int yPos;
}
}
What you're describing is what game developers call spritesheets, tilesheets, tilesets, or whatever.
The general idea behind all of these is you have one image file of a certain type and inside that image you create subimages which can be pulled out and used individually in your program. Usually in video games these are of a consistent size but they don't have to be.
Check out the answer on this link: How to extract part of this image in Java? The answer here explains how to split an image up into several parts.
If you can't find what you're looking for there look up how game developers handle spritesheets for example to get an idea of how they do it.
I’m relatively new to ImageMagick and java and working on a project to display text around the outside of a circle centered at 0 degrees on the circle using ImageMagick 6.3.9 Q16 and jmagick 6.3.9 Q16 on windows. We’re porting existing image magick code that does this from PHP MagickWand but the placement of each letter on the arc of the circle comes out a little off in the java version I think because of the following difference.
In MagickWand, it’s placed on the arc by this one line of code which uses a float x, y coordinate value and float angle value (for greater precision) for annotating the drawing wand (equivalent of DrawInfo in jmagick) and works beautifully:
MagickAnnotateImage($magick_wand, $drawing_wand, $origin_x + $x, $origin_y - $y, $angle, $character);
In jmagick though, the annotateImage method only takes one argument which is the DrawInfo so I ended up with what I think is the only other alternative, the compositeImage method. So in order to do that, I’m drawing each character as separate draw info, then annotating that to a transparent png image, then rotating that image via rotateImage method, then using compositeImage to place it on my canvas image but compositeImage only deals with x & y as int values (and doesn’t consider angle) so I’m rounding my x & y double values (to get same number of decimals or more like php version is using just to rule that out) at that point which I suspect is the main reason it’s placing the characters a little off on the circle.
My code performing the work is the following where Article is a local path to a font file (ex: E:\WCDE_ENT70\workspace\Stores\WebContent\AdminArea\CoordsCenterSection\fonts\ARIALN.TTF), nameNumStr is the string to render on the circle (ex: SAMUELSON), fsize is the point size of the font (ex: 32), colorStr is font color name (ex: black), radVal is radius (ex: 120), poix is x origin start coordinate (ex: 150), poiy is y origin start coordinate (ex: 150):
public byte[] getArcedImage(String Article, String nameNumStr, int fsize, String colorStr, int radVal, int poix, int poiy)
{
try {
Font f = null;
try {
f = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(Article.replaceAll("%20"," ")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (FontFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String fontName = f.getName();
// Use awt's font metrics since jmagick doesn't have font metrics built in like php magickwand does
FontMetrics fm = createFontMetrics(new Font(fontName, Font.PLAIN, fsize));
int strImgW = fm.stringWidth(nameNumStr);
int strImgH = fm.getHeight();
String spacerImg = "E:\\WCDE_ENT70\\workspace\\Stores\\WebContent\\AdminArea\\CoordsCenterSection\\images\\600x600.png";
//Read in large 600 png first as our main canvas
ImageInfo bi = new ImageInfo(spacerImg);
MagickImage bmi = new MagickImage(bi);
// Make canvas image transparent
bmi.setMatte(true);
bmi.setBackgroundColor(PixelPacket.queryColorDatabase("#FFFF8800"));
//defaults or param vals
final int radius = radVal;
final int origin_x = poix;
final int origin_y = poiy;
final int center_text_on = 0;
final int charXGeom = 150;
final int charYGeom = 150;
double circumference = 0;
double percentage = 0;
double degrees = 0;
double start = 0;
double current_degree = 0;
double angle = 0;
double angle_adjustment = 0;
double character_center = 0;
/**
* Calculate the circumference of the drawn circle and label the image
* with it.
*/
circumference = (2 * Math.PI * radius);
/**
* Calculate the percentage of the circumference that the string will
* consume.
*/
percentage = strImgW / circumference;
/**
* Convert this percentage into something practical - degrees.
*/
degrees = 360 * percentage;
/**
* Because the string is centered, we need to calculate the starting point
* of the string by subtracting half of the required degrees from the
* anticipated center mark.
*/
start = center_text_on - (degrees / 2);
/**
* Initialize our traversal starting point.
*/
current_degree = start;
//
ImageInfo ci = null;
MagickImage cmi = null;
double x = 0;
double y = 0;
int finalStrWidth = 0;
int charImgW = 0;
int charImgH = 0;
for (int i=0; i<nameNumStr.length(); i++)
{
/**
* Isolate the appropriate character.
*/
String charVal = nameNumStr.substring(i, i+1);
charImgW = fm.stringWidth(charVal);
charImgH = strImgH;
ci = new ImageInfo(spacerImg);
cmi = new MagickImage(ci);
// Create Rectangle for cropping character image canvas to final width and height
Rectangle charRect = new Rectangle(0,0,charImgW,charImgH);
// Crop image to final width and height
cmi = cmi.cropImage(charRect);
// Make image transparent
cmi.setMatte(true);
cmi.setBackgroundColor(PixelPacket.queryColorDatabase("#FFFF8800"));
// Set a draw info for each character
DrawInfo cdi = new DrawInfo(ci);
// Set Opacity
cdi.setOpacity(0);
// Set Gravity
cdi.setGravity(GravityType.CenterGravity);
// Set Fill Color
cdi.setFill(PixelPacket.queryColorDatabase(colorStr));
// Set Font Size
cdi.setPointsize(fsize);
// Set Font
cdi.setFont(Article.replaceAll("%20"," "));
// Set the text
cdi.setText(charVal);
// Make the text smoother
cdi.setTextAntialias(true);
// Annotate the draw info to make the character image
cmi.annotateImage(cdi);
// For debug purposes
finalStrWidth += charImgW;
/**
* Calculate the percentage of the circumference that the character
* will consume.
*/
percentage = charImgW / circumference;
/**
* Convert this percentage into something practical - degrees.
*/
degrees = 360 * percentage;
/**
* Calculate the x and y axis adjustments to make, based on the origin
* of the circle, so we can place each letter.
*/
x = radius * Math.sin(Math.toRadians(current_degree));
y = radius * Math.cos(Math.toRadians(current_degree));
// Rotate the character image to the angle
cmi = cmi.rotateImage(angle);
// Composite character image to main canvas image
bmi.compositeImage(CompositeOperator.HardLightCompositeOp, cmi, (int)Math.round((origin_x+x)), (int)Math.round((origin_y-y)));
// Increment the degrees
current_degree += degrees;
}
bmi = bmi.trimImage();
byte[] pi = bmi.imageToBlob(ci);
return pi;
} catch (MagickException e) {
e.printStackTrace();
return null;
}
}
private FontMetrics createFontMetrics(Font font)
{
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
g.dispose();
bi = null;
return fm;
}
private Rectangle2D createFontRectangle(Font font, String strVal)
{
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
Rectangle2D rect = fm.getStringBounds(strVal, g);
g.dispose();
bi = null;
return rect;
}
I've since found that it’s possible to use DrawInfo’s setGeometry method to set the x, y and saw on the one example I found on jmagick.org's wiki that it supposedly can be used for much more than x, y placement but can’t find any other examples or documentation showing how else it can be used (hopefully for specifying an angle as well).
I’m not positive but it seems that setGeometry would be the only way to potentially specify an angle since jmagick’s implementation of annotateImage only takes a Draw Info as it’s argument.
Does anyone know a way to use DrawInfo’s setGeometry method to set the x, y and angle? I think it may solve my problem. Also, if anyone has any working example of using jmagick to draw text around a circle that they’d be willing to share, I’d be greatly appreciative.
Thanks
I'm trying to have an image scale to a certain size depending on the horizontal size sent to an update function, but the following code doesnt seem to size the image correctly.
EDIT: The code:
public class GlassesView extends View {
private Paint paint;
private BitmapFactory.Options options;
private Bitmap bitmapOrg;
private Bitmap target;
private Bitmap bitmapRev;
private Bitmap resizedBitmap;
private int currY;
public int glassesX;
public int glassesY;
public float glassesSizeX;
public float glassesSizeY;
private boolean drawGlasses;
private boolean glassesMirrored;
public GlassesView(Context context) {
super(context);
paint = new Paint();
paint.setDither(false);
paint.setAntiAlias(false);
options = new BitmapFactory.Options();
options.inDither = false;
options.inScaled = false;
bitmapOrg = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),
R.drawable.micro_glasses, options), 32, 5, false);
bitmapRev = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),
R.drawable.glasses_reverse, options), 32, 5, false);
drawGlasses = false;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(target, 0, 0, paint);
boolean moving = currY < glassesY;
if (moving) {
currY++;
}
if (drawGlasses) {
int newWidth = resizedBitmap.getWidth();
int newHeight = resizedBitmap.getHeight();
Paint bluey = new Paint();
bluey.setColor(Color.argb(64, 0, 0, 255));
canvas.drawRect(new Rect(glassesX, currY, glassesX + newWidth,
currY + newHeight), bluey);
canvas.drawBitmap(resizedBitmap, glassesX, currY, paint);
}
if (moving) {
invalidate();
}
}
public void drawGlasses(int x1, int x2, int y, boolean mirror) {
drawGlasses = true;
glassesMirrored = mirror;
if (!mirror) {
glassesSizeX = (float) (x2 - x1) / (float) (25 - 16);
glassesSizeY = glassesSizeX;
glassesY = y - (int)(1*glassesSizeX);
glassesX = (int) (x1 - (glassesSizeX * 16));
} else {
glassesSizeX = (float) (x1 - x2) / (float) (25 - 16);
glassesSizeY = glassesSizeX;
glassesY = y - (int)(1*glassesSizeX);
glassesX = (int) (x1 - (glassesSizeX * 16));
}
currY = -1;
if (!glassesMirrored) {
resizedBitmap = Bitmap.createScaledBitmap(bitmapOrg,
(int) (bitmapOrg.getWidth() * glassesSizeX),
(int) (bitmapOrg.getHeight() * glassesSizeY), false);
} else {
resizedBitmap = Bitmap.createScaledBitmap(bitmapRev,
(int) (bitmapRev.getWidth() * glassesSizeX),
(int) (bitmapRev.getHeight() * glassesSizeY), false);
}
}
public void setTargetPic(Bitmap targetPic) {
target = targetPic;
}
}
The result. (The blue rectangle being the bounding box of the image's intended size)
Which part am I going wrong at?
EDIT 2:
Here are the glasses:
EDIT 3:
Out of curiousity, I ran it on my actual phone, and got a much different result, the image was stretched passed the intended blue box.
EDIT 4:
I tried running the app on a few emulators to see if it was an Android version incompatibility thing, but they all seemed to work perfectly. The scaling issue only occurs on my phone (Vibrant, rooted, CM7) and my cousin's (Droid, also rooted). These are the only physical devices I have tested on, but they both seem to have the same issue.
I'd really appreciate if someone could help me out here, this is a huge roadblock in my project and no other forums or message groups are responding.
EDIT 5:
I should mention that in update 4, the code changed a bit, which fixed the problem in the emulators as I stated, but doesn't work on physical devices. Changes are updated in the code above. *desperate for help* :P
EDIT 6:
Yet another update, I tested the same code on my old G1, and it works perfectly as expected. I have absolutely no clue now.
have you been using the /res/drawable-nodpi/ directory to store your images?
Apparently if you use the /drawable-ldpi/, /drawable-mdpi/ or /drawable-hdpi/ folders, Android will apply scaling to the image when you load it depending on the device. The reason your G1 may work is that it may not require any scaling depending on the folder you used.
Also, your IMGUR links are broken... also your code doesn't seem correct... you call DrawGlasses with no arguments.
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, glassesWidth,
glassesHeight, matrix, false);
Change the last parameter from false to true.
you can have a try.
The follow is i used zoom in method by scale:
private void small() {
int bmpWidth=bmp.getWidth();
int bmpHeight=bmp.getHeight();
Log.i(TAG, "bmpWidth = " + bmpWidth + ", bmpHeight = " + bmpHeight);
/* 设置图片缩小的比例 */
double scale=0.8;
/* 计算出这次要缩小的比例 */
scaleWidth=(float) (scaleWidth*scale);
scaleHeight=(float) (scaleHeight*scale);
/* 产生reSize后的Bitmap对象 */
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizeBmp = Bitmap.createBitmap(bmp,0,0,bmpWidth,
bmpHeight,matrix,true);
Found what was wrong. The image has to be in a folder called "drawable-nodpi", otherwise it will be scaled depending on DPI.