source: trunk/src/org/expeditee/items/Picture.java@ 1441

Last change on this file since 1441 was 1441, checked in by bnemhaus, 5 years ago

Copying images in Expeditee now duplicates the associated file on the filesystem.
Also changed USER_NAME back to USER.NAME on David's direction.

File size: 27.8 KB
Line 
1/**
2 * Picture.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.items;
20
21import java.io.File;
22import java.io.IOException;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.nio.file.Paths;
26import java.nio.file.StandardCopyOption;
27import java.text.DecimalFormat;
28
29import org.apache.commons.cli.CommandLine;
30import org.apache.commons.cli.CommandLineParser;
31import org.apache.commons.cli.GnuParser;
32import org.apache.commons.cli.Options;
33import org.apache.commons.cli.ParseException;
34import org.expeditee.core.Clip;
35import org.expeditee.core.Colour;
36import org.expeditee.core.Dimension;
37import org.expeditee.core.EnforcedClipStack.EnforcedClipKey;
38import org.expeditee.core.Image;
39import org.expeditee.core.Point;
40import org.expeditee.core.Stroke;
41import org.expeditee.core.bounds.AxisAlignedBoxBounds;
42import org.expeditee.core.bounds.PolygonBounds;
43import org.expeditee.encryption.core.EncryptedImage;
44import org.expeditee.encryption.items.surrogates.Label;
45import org.expeditee.encryption.items.surrogates.Label.LabelInfo;
46import org.expeditee.encryption.items.surrogates.Label.LabelResult;
47import org.expeditee.gio.EcosystemManager;
48import org.expeditee.gio.GraphicsManager;
49import org.expeditee.gui.DisplayController;
50import org.expeditee.gui.FrameIO;
51import org.expeditee.gui.FrameUtils;
52import org.expeditee.gui.MessageBay;
53import org.expeditee.gui.management.ResourceUtil;
54
55/**
56 * This class represents an Image loaded from a file which is shown on the
57 * screen. Loading of the Image from disk occurs in the constructor, and takes
58 * approximately one second per mb of the Image file size. <br>
59 * <br>
60 * Currently Supported (Tested) Image formats:<br>
61 * BMP<br>
62 * JPG<br>
63 * GIF<br>
64 * GIF (Animated)<br>
65 * <br>
66 * Currently only the default size of the Image is supported, but future
67 * versions may support scaling.
68 *
69 * @author jdm18
70 *
71 */
72public class Picture extends XRayable {
73
74 public static final String REDACTED_IMAGE_NAME = "expeditee_noise.encrypted";
75
76 private static final float CROPPING_COMPOSITE_ALPHA = 0.5f;
77
78 private static final int MINIMUM_WIDTH = 10;
79
80 public static final int WIDTH = 0;
81
82 public static final int RATIO = 1;
83
84 protected Image _image = null;
85
86 private int _scaleType = RATIO;
87
88 private float _scale = 1.0f;
89
90 // Start of the crop relative to START
91 private Point _cropStart = null;
92
93 // Start of the crop relative to END
94 private Point _cropEnd = null;
95
96 private Point _start = new Point(0, 0);
97
98 private Point _end = new Point(0, 0);
99
100 private double _rotate = 0;
101
102 private boolean _flipX = false;
103 private boolean _flipY = false;
104
105 private boolean _showCropping = false;
106
107 protected Integer _anchorLeft = null;
108 protected Integer _anchorTop = null;
109
110 private String _path = "";
111
112 private String _size = "";
113
114 private String _fileName = null;
115
116 protected Picture(Text source, Image image) {
117 super(source);
118 _image = image;
119
120 refresh();
121
122 if (_image != null) {
123 // Should parsing for minus options also be done?
124 // To be honest, looking at the code, can't really see how _size can be anything but
125 // the empty string at this stage of calling the constructor, although it does have
126 // the 'side' effect of setting other things (such as _start, _end, and _scale)
127 parseSize();
128 }
129 }
130
131 /**
132 * Creates a new Picture from the given path. The ImageObserver is optional
133 * and can be set to NULL. <br>
134 * Note: It is assumed that the file described in path has already been
135 * checked to exist.
136 *
137 * @param source
138 * The Text Item that was used to create this Picture
139 * @param fileName
140 * the name of the file as it should be displayed in the source
141 * text
142 * @param path
143 * The Path of the Image to load from disk.
144 * @param observer
145 * The ImageObserver to assign when painting the Image on the
146 * screen.
147 */
148 public Picture(Text source, String fileName, String path, String size)
149 {
150 super(source);
151 _fileName = fileName;
152 _path = path;
153
154 String size_without_options = parseMinusOptions(size);
155 _size = size_without_options;
156
157 refresh();
158 parseSize();
159 }
160
161 public boolean isNoise() {
162 return _fileName.equals(REDACTED_IMAGE_NAME);
163 }
164
165
166
167 protected String getImageSize() {
168 return _size;
169 }
170
171 protected String parseMinusOptions(String cmd_line) {
172
173 String[] tokens = Text.parseArgsApache(cmd_line);
174
175 // make anything starting with a '-' lowercase
176 for (int i=0; i<tokens.length; i++) {
177 if (tokens[i].startsWith("-")) {
178 tokens[i] = tokens[i].toLowerCase();
179 }
180 }
181
182 // create the command line parser
183 CommandLineParser parser = new GnuParser();
184
185 // create the Options
186 Options options = new Options();
187 options.addOption( "al", "anchorleft", true, "Anchor the vertical left-hand edge of the interactive widget to the value provided " );
188 options.addOption( "at", "anchortop", true, "Anchor the vertical top edge of the interactive widget to the value provided " );
189
190 CommandLine core_line;
191 try {
192 // parse the command line arguments
193 core_line = parser.parse( options, tokens );
194
195 // Update tokens to be version with the options removed
196 tokens = core_line.getArgs();
197
198 }
199 catch( ParseException exp ) {
200 System.err.println( "Unexpected exception:" + exp.getMessage() );
201 core_line = null;
202
203 }
204
205 // Apply any anchor values supplied
206
207 if(core_line.hasOption( "anchorleft" ) ) {
208 String al_str = core_line.getOptionValue( "anchorleft" );
209
210 _anchorLeft = Integer.parseInt(al_str);
211 }
212
213 if(core_line.hasOption( "anchortop" ) ) {
214 String at_str = core_line.getOptionValue( "anchortop" );
215
216 _anchorTop = Integer.parseInt(at_str);
217 }
218
219
220 return String.join(" ", tokens);
221 }
222 protected void parseSize() {
223 String size = getImageSize();
224
225 if (_end.getX() != 0 || _end.getY() != 0)
226 return;
227
228 // set the default values for start and end
229 _start.set(0, 0);
230 if (_image == null)
231 _end.set(0, 0);
232 else
233 _end.set(_image.getWidth(), _image.getHeight());
234 size = size.trim();
235 String sizeLower = size.toLowerCase();
236 String[] values = size.split("\\s+");
237 // Now get the cropping values if there are any
238 try {
239 if (values.length > 2) {
240 int startX = Integer.parseInt(values[1]);
241 int startY = Integer.parseInt(values[2]);
242 _start = new Point(startX, startY);
243 if (values.length > 4) {
244 int endX = Integer.parseInt(values[3]);
245 int endY = Integer.parseInt(values[4]);
246 _end = new Point(endX, endY);
247 }
248 scaleCrop();
249 }
250 } catch (Exception e) {
251 }
252
253 if(sizeLower.contains("flipx")) {
254 _flipX = true;
255 }
256
257 if(sizeLower.contains("flipy")) {
258 _flipY = true;
259 }
260
261 int index = sizeLower.indexOf("rotation=");
262 if(index != -1) {
263 int tmp = sizeLower.indexOf(" ", index);
264 String rotation;
265 if(tmp == -1) {
266 rotation = sizeLower.substring(index + "rotation=".length());
267 } else {
268 rotation = sizeLower.substring(index + "rotation=".length(), index + tmp);
269 }
270 _rotate = Double.parseDouble(rotation);
271 }
272
273 try {
274 if (size.length() == 0) {
275 size = "" + _image.getWidth();
276 _source.setText(getTagText() + size);
277 return;
278 }
279 size = values[0];
280 // parse width or ratio from text
281 if (size.contains(".")) {
282 // this is a ratio
283 _scale = Float.parseFloat(size);
284 _scaleType = RATIO;
285 } else if (size.length() > 0) {
286 // this is an absolute width
287 int width = Integer.parseInt(size);
288 _scaleType = WIDTH;
289 setWidth(width);
290 }
291 } catch (Exception e) {
292 _scale = 1F;
293 }
294 }
295
296 public void setStartCrop(Point p)
297 {
298 if (p != null) setStartCrop(p.getX(), p.getY());
299 }
300
301 public void setStartCrop(int x, int y) {
302 invalidateCroppedArea();
303 _cropStart = new Point(x - getX(), y - getY());
304 invalidateCroppedArea();
305 }
306
307 public void setEndCrop(Point p)
308 {
309 if (p != null) setEndCrop(p.getX(), p.getY());
310 }
311
312 public void setEndCrop(int x, int y) {
313 invalidateCroppedArea();
314 _cropEnd = new Point(x - getX(), y - getY());
315 invalidateCroppedArea();
316 }
317
318 private void invalidateCroppedArea() {
319 if (_cropStart != null && _cropEnd != null) {
320 Point topLeft = getTopLeftCrop();
321 Point bottomRight = getBottomRightCrop();
322 int startX = getX() + topLeft.getX() - _highlightThickness;
323 int startY = getY() + topLeft.getY() - _highlightThickness;
324 int border = 2 * _highlightThickness;
325 // TODO: Why invalidate specific area just before invalidateAll? cts16
326 invalidate(new AxisAlignedBoxBounds(startX, startY,
327 bottomRight.getX() - topLeft.getX() + 2 * border, bottomRight.getY() - topLeft.getY() + 2 * border));
328 invalidateAll();
329 } else {
330 invalidateAll();
331 }
332 }
333
334 public Point getTopLeftCrop() {
335 return new Point(Math.min(_cropStart.getX(), _cropEnd.getX()), Math.min(
336 _cropStart.getY(), _cropEnd.getY()));
337 }
338
339 public Point getBottomRightCrop()
340 {
341 return new Point(Math.max(_cropStart.getX(), _cropEnd.getX()), Math.max(_cropStart.getY(), _cropEnd.getY()));
342 }
343
344 public void setShowCrop(boolean value) {
345 // invalidateCroppedArea();
346 _showCropping = value;
347 invalidateCroppedArea();
348 }
349
350 public boolean isBeingCropped()
351 {
352 return (_cropStart != null && _cropEnd != null);
353 }
354
355 public boolean isCropTooSmall()
356 {
357 if (!isBeingCropped()) return true;
358
359 int cropWidth = Math.abs(_cropEnd.getX() - _cropStart.getX());
360 int cropHeight = Math.abs(_cropEnd.getY() - _cropStart.getY());
361
362 return cropWidth < MINIMUM_WIDTH || cropHeight < MINIMUM_WIDTH;
363 }
364
365 public void clearCropping() {
366 invalidateCroppedArea();
367 _cropStart = null;
368 _cropEnd = null;
369 setShowCrop(false);
370 }
371
372 public PolygonBounds updateBounds()
373 {
374 if (_image == null) {
375 refresh();
376 parseSize();
377 }
378
379 Point[] ori = new Point[4];
380 Point centre = new Point();
381
382 int base_x = (_anchorLeft!=null) ? _anchorLeft : _source.getX();
383 int base_y = (_anchorTop!=null) ? _anchorTop : _source.getY();
384
385 if (_cropStart == null || _cropEnd == null) {
386 int width = getWidth();
387 int height = getHeight();
388
389 centre.setX(base_x + width / 2);
390 centre.setY(base_y + height / 2);
391
392 int xdiff = -MARGIN_RIGHT; // -getLeftMargin();
393
394 // extra pixel around the image so the highlighting is visible
395// _poly.addPoint(_source.getX() + 1 + xdiff, _source.getY() - 1);
396// _poly.addPoint(_source.getX() + width, _source.getY() - 1);
397// _poly.addPoint(_source.getX() + width, _source.getY() + height);
398// _poly.addPoint(_source.getX() + 1 + xdiff, _source.getY() + height);
399
400 ori[0] = new Point(base_x + 1 + xdiff, base_y - 1);
401 ori[1] = new Point(base_x + width, base_y - 1);
402 ori[2] = new Point(base_x + width, base_y + height);
403 ori[3] = new Point(base_x + 1 + xdiff, base_y + height);
404
405 } else {
406 Point topLeft = getTopLeftCrop();
407 Point bottomRight = getBottomRightCrop();
408
409 centre.setX(base_x + (bottomRight.getX() - topLeft.getX()) / 2);
410 centre.setY(base_y + (bottomRight.getY() - topLeft.getY()) / 2);
411
412 AxisAlignedBoxBounds clip = new AxisAlignedBoxBounds(topLeft.getX() + base_x,
413 topLeft.getY() + base_y, bottomRight.getX() - topLeft.getX(),
414 bottomRight.getY() - topLeft.getY());
415// _poly.addPoint((int) clip.getMinX() - 1, (int) clip.getMinY() - 1);
416// _poly.addPoint((int) clip.getMinX() - 1, (int) clip.getMaxY());
417// _poly.addPoint((int) clip.getMaxX(), (int) clip.getMaxY());
418// _poly.addPoint((int) clip.getMaxX(), (int) clip.getMinY() - 1);
419
420 ori[0] = new Point((int) clip.getMinX() - 1, (int) clip.getMinY() - 1);
421 ori[1] = new Point((int) clip.getMinX() - 1, (int) clip.getMaxY());
422 ori[2] = new Point((int) clip.getMaxX(), (int) clip.getMaxY());
423 ori[3] = new Point((int) clip.getMaxX(), (int) clip.getMinY() - 1);
424
425 }
426
427 PolygonBounds poly = new PolygonBounds();
428 for (Point p : ori) {
429 poly.addPoint(p);
430 }
431 poly.rotate(Math.PI * _rotate / 180, centre);
432
433 return poly.close();
434 }
435
436 @Override
437 public double getEnclosedArea() {
438 return getWidth() * getHeight();
439 }
440
441 @Override
442 public void setWidth(Integer width) {
443 _scale = width * 1F / (_end.getX() - _start.getX());
444 }
445
446 public Point getStart() {
447 return _start;
448 }
449
450 public Point getEnd() {
451 return _end;
452 }
453
454 /**
455 * Gets the width with which the picture is displayed on the screen.
456 */
457 @Override
458 public Integer getWidth() {
459 return Math.round(getUnscaledWidth() * _scale);
460 }
461
462 /**
463 * Gets the height with which the picture is displayed on the screen.
464 */
465 @Override
466 public int getHeight() {
467 return Math.round(getUnscaledHeight() * _scale);
468 }
469
470 /**
471 * Dont paint links in audience mode for images.
472 */
473 @Override
474 protected void paintLink()
475 {
476 if (DisplayController.isAudienceMode()) return;
477 super.paintLink();
478 }
479
480 /**
481 * Paint the image repeatedly tiled over the drawing area.
482 */
483 public void paintImageTiling()
484 {
485 if (_image == null) return;
486
487 int iw = _image.getWidth();
488 int ih = _image.getHeight();
489 if(iw <= 0 || ih <= 0) return;
490
491 int base_x = (_anchorLeft != null) ? _anchorLeft : _source.getX();
492 int base_y = (_anchorTop != null) ? _anchorTop : _source.getY();
493
494 int dX1 = base_x;
495 int dY1 = base_y;
496 int dX2 = base_x + getWidth();
497 int dY2 = base_y + getHeight();
498
499 Image tmp = Image.createImage(getWidth(), getHeight());
500 EcosystemManager.getGraphicsManager().pushDrawingSurface(tmp);
501
502 int offX = (tmp.getWidth() - getWidth()) / 2;
503 int offY = (tmp.getHeight() - getHeight()) / 2;
504
505 int cropStartX = _start.getX();
506 int cropEndX = _end.getX();
507 if(cropEndX > iw) {
508 cropEndX = iw;
509 }
510
511 for(int x = dX1; x < dX2; ) {
512 // end - start = (cropEnd - cropStart) * scale
513 // => cropEnd = cropStart + (end - start) / scale
514 int w = (int) ((cropEndX - cropStartX) * _scale);
515 int endX = x + w;
516 if(endX > dX2) {
517 endX = dX2;
518 cropEndX = cropStartX + (int) ((dX2 - x) / _scale);
519 }
520
521 int cropStartY = _start.getY();
522 int cropEndY = _end.getY();
523 if(cropEndY > ih) {
524 cropEndY = ih;
525 }
526
527 for(int y = dY1; y < dY2; ) {
528 int h = (int) ((cropEndY - cropStartY) * _scale);
529 int endY = y + h;
530 if(endY > dY2) {
531 endY = dY2;
532 cropEndY = cropStartY + (int) ((dY2 - y) / _scale);
533 }
534
535 int sx = _flipX ? cropEndX : cropStartX;
536 int ex = _flipX ? cropStartX : cropEndX;
537 int sy = _flipY ? cropEndY : cropStartY;
538 int ey = _flipY ? cropStartY : cropEndY;
539
540 Point topLeft = new Point(x - dX1 + offX, y - dY1 + offY);
541 Dimension size = new Dimension(endX - x, endY - y);
542 Point cropTopLeft = new Point(sx, sy);
543 Dimension cropSize = new Dimension(ex - sx, ey - sy);
544 if (cropSize.width > 0 && cropSize.height > 0) {
545 EcosystemManager.getGraphicsManager().drawImage(_image, topLeft, size, 0.0, cropTopLeft, cropSize);
546 }
547
548 cropStartY = 0;
549 cropEndY = ih;
550
551 y = endY;
552 }
553
554 cropStartX = 0;
555 cropEndX = iw;
556
557 x = endX;
558 }
559
560 EcosystemManager.getGraphicsManager().popDrawingSurface();
561 EcosystemManager.getGraphicsManager().drawImage(tmp, new Point(dX1, dY1), null, Math.PI * _rotate / 180);
562 tmp.releaseImage();
563 }
564
565 @Override
566 public void paint()
567 {
568 if (_image == null) return;
569
570 paintLink();
571
572 GraphicsManager g = EcosystemManager.getGraphicsManager();
573
574 // if we are showing the cropping
575 if (_showCropping && !isCropTooSmall()) {
576 // show the uncropped area as transparent
577 g.setCompositeAlpha(CROPPING_COMPOSITE_ALPHA);
578 paintImageTiling();
579 g.setCompositeAlpha(1.0f);
580
581 // show the cropped area normally
582 Point topLeft = getTopLeftCrop();
583 Point bottomRight = getBottomRightCrop();
584 int base_x = (_anchorLeft != null) ? _anchorLeft : _source.getX();
585 int base_y = (_anchorTop != null) ? _anchorTop : _source.getY();
586
587 Clip clip = new Clip(new AxisAlignedBoxBounds( base_x + topLeft.getX(),
588 base_y + topLeft.getY(),
589 bottomRight.getX() - topLeft.getX(),
590 bottomRight.getY() - topLeft.getY()));
591 EnforcedClipKey key = g.pushClip(clip);
592 paintImageTiling();
593 g.popClip(key);
594
595 // Draw an outline for the crop selection box
596 g.drawRectangle(clip.getBounds(), 0.0, null, getPaintHighlightColor(), HIGHLIGHT_STROKE, null);
597
598 // otherwise, paint normally
599 } else {
600 paintImageTiling();
601 }
602
603 PolygonBounds poly = (PolygonBounds) getBounds();
604
605 if (hasVisibleBorder()) {
606 Stroke borderStroke = new Stroke(getThickness(), DEFAULT_CAP, DEFAULT_JOIN);
607 g.drawPolygon(poly, null, null, 0.0, null, getPaintBorderColor(), borderStroke);
608 }
609
610 if (isHighlighted()) {
611 Stroke borderStroke = new Stroke(1, DEFAULT_CAP, DEFAULT_JOIN);
612 g.drawPolygon(poly, null, null, 0.0, null, getHighlightColor(), borderStroke);
613 }
614 }
615
616 @Override
617 public Colour getHighlightColor()
618 {
619 if (_highlightColour.equals(getBorderColor())) return ALTERNATE_HIGHLIGHT;
620 return _highlightColour;
621 }
622
623 protected Picture createPicture()
624 {
625 return ItemUtils.CreatePicture((Text) _source.copy());
626 }
627
628 @Override
629 public Picture copy() {
630 Picture p = createPicture();
631 p._image = _image;
632 p._highlightMode = _highlightMode;
633
634 String copyFileName = _fileName;
635 try {
636 String name = getName();
637 Path imageFilePath = ResourceUtil.resolveImagePath(Paths.get(name), this.getSource().getParent());
638 Path imageDirectoryPath = imageFilePath.getParent();
639 String fileName = name.substring(0, name.indexOf('.'));
640 String suffix = name.substring(name.indexOf('.'));
641 File copiedFile = File.createTempFile(fileName, suffix, imageDirectoryPath.toFile());
642 Path newFilePath = Files.copy(imageFilePath, Paths.get(copiedFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING);
643 Path newFilePathResolved = ResourceUtil.resolveImagePath(newFilePath.getFileName(), this.getSource().getParent());
644 Path newFilePathRelativeToExpediteeHome = ResourceUtil.relativiseImagePath(newFilePathResolved);
645 copyFileName = newFilePathRelativeToExpediteeHome.toString();
646 } catch (IOException e) {
647 MessageBay.displayMessage("Unable to duplicate image file, new copy refers to existing image file.");
648 MessageBay.displayMessage("Caused by IOException with message: " + e.getMessage());
649 }
650
651 // Doing Duplicate item duplicates link mark which we dont want to do
652 // when in audience mode because the linkMark will be copied incorrectly
653 // Get all properties from the source
654
655 if (!isCropTooSmall() && _cropStart != null && _cropEnd != null) {
656 assert (_cropEnd != null);
657 // make the start be the top left
658 // make the end be the bottom right
659 Point topLeft = getTopLeftCrop();
660 Point bottomRight = getBottomRightCrop();
661 int startX = Math.round(topLeft.getX() / _scale) + _start.getX();
662 int startY = Math.round(topLeft.getY() / _scale) + _start.getY();
663 int endX = Math.round(bottomRight.getX() / _scale + _start.getX());
664 int endY = Math.round(bottomRight.getY() / _scale + _start.getY());
665 int width = _image.getWidth();
666 int height = _image.getHeight();
667 // adjust our start and end if the user has dragged outside of the
668 // shape
669 if (endX > width) {
670 endX = width;
671 }
672 if (endY > height) {
673 endY = height;
674 }
675 if (startX < 0) {
676 startX = 0;
677 }
678 if (startY < 0) {
679 startY = 0;
680 }
681 p._start = new Point(startX, startY);
682 p._end = new Point(endX, endY);
683 int base_x = (_anchorLeft!=null) ? _anchorLeft : _source.getX();
684 int base_y = (_anchorTop!=null) ? _anchorTop : _source.getY();
685 p._source.setPosition(topLeft.getX() + base_x, topLeft.getY() + base_y);
686 } else {
687 p._start = new Point(_start);
688 p._end = new Point(_end);
689 }
690 p._scale = _scale;
691 p._scaleType = _scaleType;
692 p._path = _path;
693 p._fileName = copyFileName;
694
695 p.updateSource();
696 p.invalidateBounds();
697
698 return p;
699 }
700
701 public float getScale() {
702 return _scale;
703 }
704
705 public void setScale(float scale) {
706 _scale = scale;
707 }
708
709 public void scaleCrop() {
710 // scale crop values to within image bounds
711 int iw = _image.getWidth();
712 int ih = _image.getHeight();
713 if(iw > 0 || ih > 0) {
714 while(_start.getX() >= iw) {
715 _start.setX(_start.getX() - iw);
716 _end.setX(_end.getX() - iw);
717 }
718 while(_start.getY() >= ih) {
719 _start.setY(_start.getY() - ih);
720 _end.setY(_end.getY() - ih);
721 }
722 while(_start.getX() < 0) {
723 _start.setX(_start.getX() + iw);
724 _end.setX(_end.getX() + iw);
725 }
726 while(_start.getY() < 0) {
727 _start.setY(_start.getY() + ih);
728 _end.setY(_end.getY() + ih);
729 }
730 }
731 }
732
733 public void setCrop(int startX, int startY, int endX, int endY) {
734 _start = new Point(startX, startY);
735 _end = new Point(endX, endY);
736 updateSource();
737 }
738
739 @Override
740 public float getSize() {
741 return _source.getSize();
742 }
743
744 @Override
745 public void setSize(float size) {
746 float diff = size - _source.getSize();
747 float oldScale = _scale;
748
749 float multiplier = (1000F + diff * 40F) / 1000F;
750 _scale = _scale * multiplier;
751
752 // picture must still be at least XX pixels wide
753 if (getWidth() < MINIMUM_WIDTH) {
754 _scale = oldScale;
755 } else {
756 _source.translate(EcosystemManager.getInputManager().getCursorPosition(), multiplier);
757 }
758 updateSource();
759 invalidateBounds();
760 // Make sure items that are resized display the border
761 invalidateAll();
762 }
763
764 @Override
765 public void setAnnotation(boolean val) {
766 }
767
768 /**
769 * Returns the Image that this Picture object is painting on the screen.
770 * This is used by Frame to repaint animated GIFs.
771 *
772 * @return The Image that this Picture object represents.
773 */
774 public Image getImage() {
775 return _image;
776 }
777
778 public Image getCroppedImage() {
779 if (_image == null)
780 return null;
781 if (!isCropped()) {
782 return _image;
783 }
784
785 return Image.createImageAsCroppedCopy(_image, _start.getX(), _start.getY(), getUnscaledWidth(), getUnscaledHeight());
786 }
787
788 public int getUnscaledWidth() {
789 return _end.getX() - _start.getX();
790 }
791
792 public int getUnscaledHeight() {
793 return _end.getY() - _start.getY();
794 }
795
796 /**
797 * @return true if this is a cropped image.
798 */
799 public boolean isCropped() {
800 return (_end.getX() != 0 && _end.getX() != _image.getWidth()) || (_end.getY() != 0 && _end.getY() != _image.getHeight()) || _start.getY() != 0 || _start.getX() != 0;
801 }
802
803 @Override
804 public boolean refresh() {
805 if (isNoise()) {
806 _image = EncryptedImage.getNoise();
807 } else {
808 String encryptionLabel = _source.getEncryptionLabel();
809 if (encryptionLabel == null || encryptionLabel.isEmpty()) {
810 _image = Image.getImage(_path);
811 } else {
812 LabelInfo result = Label.getLabel(encryptionLabel);
813 if (result.is(LabelResult.SuccessResolveLabelToKey)) {
814 _image = EncryptedImage.getImage(_path, result.key);
815 } else {
816 MessageBay.displayMessage(result.toString());
817 _image = EncryptedImage.getNoise();
818 }
819 }
820 }
821
822 return true;
823 }
824
825 @Override
826 protected int getLinkYOffset() {
827 return getBoundsHeight() / 2;
828 }
829
830 @Override
831 public void setLinkMark(boolean state) {
832 // TODO use the more efficient invalidiate method
833 // The commented code below is not quite working
834 // if(!state)
835 // invalidateCommonTrait(ItemAppearence.LinkChanged);
836 _source.setLinkMark(state);
837 // if(state)
838 // invalidateCommonTrait(ItemAppearence.LinkChanged);
839 invalidateAll();
840 }
841
842 @Override
843 public void setActionMark(boolean state) {
844 // if (!state)
845 // invalidateCommonTrait(ItemAppearence.LinkChanged);
846 _source.setActionMark(state);
847 // if (state)
848 // invalidateCommonTrait(ItemAppearence.LinkChanged);
849 invalidateAll();
850 }
851
852 @Override
853 public boolean getLinkMark() {
854 return !DisplayController.isAudienceMode() && _source.getLinkMark();
855 }
856
857 @Override
858 public boolean getActionMark() {
859 return _source.getActionMark();
860 }
861
862 @Override
863 public String getName() {
864 return _fileName;
865 }
866
867 public String getPath() {
868 return _path;
869 }
870
871 /**
872 * Copies the image to the default images folder and updates the reference to it in Expeditee
873 * Used for correcting image references for FrameShare
874 */
875 public void moveToImagesFolder() {
876 File f = new File(getPath());
877 // if the file is not in the default images folder, copy it there
878 if(! f.getParentFile().equals(new File(FrameIO.IMAGES_PATH))) {
879 try {
880 File f2 = new File(FrameIO.IMAGES_PATH + f.getName());
881 FrameUtils.copyFile(f, f2, false);
882 f = f2;
883 } catch (IOException e) {
884 e.printStackTrace();
885 f = null;
886 }
887 }
888 _path = f.getPath();
889 _fileName = f.getName();
890 updateSource();
891 }
892
893 protected String getTagText() {
894 return "@i: " + _fileName + " ";
895 }
896
897 /**
898 * Updates the source text for this item to match the current size of the
899 * image.
900 *
901 */
902 private void updateSource() {
903 StringBuffer newText = new StringBuffer(getTagText());
904
905 switch (_scaleType) {
906 case (RATIO):
907 DecimalFormat format = new DecimalFormat("0.00");
908 newText.append(format.format(_scale));
909 break;
910 case (WIDTH):
911 newText.append(getWidth());
912 break;
913 }
914
915 scaleCrop();
916
917 // If the image is cropped add the position for the start and finish of
918 // the crop to the soure text
919 if (_start.getX() > 0 || _start.getY() > 0 || _end.getX() != _image.getWidth()
920 || _end.getY() != _image.getHeight()) {
921 newText.append(" ").append(_start.getX()).append(" ").append(_start.getY());
922 newText.append(" ").append(_end.getX()).append(" ").append(_end.getY());
923 }
924
925 if(_flipX) {
926 newText.append(" flipX");
927 }
928 if(_flipY) {
929 newText.append(" flipY");
930 }
931 if(Double.compare(_rotate, 0) != 0) {
932 newText.append(" rotation=" + _rotate);
933 }
934
935 _source.setText(newText.toString());
936 }
937
938 @Override
939 public void translate(Point origin, double ratio) {
940 _scale *= ratio;
941 updateSource();
942 super.translate(origin, ratio);
943 }
944
945 @Override
946 public AxisAlignedBoxBounds getDrawingArea() {
947
948 AxisAlignedBoxBounds da = super.getDrawingArea();
949
950 if (getLink() != null || hasAction()) {
951 AxisAlignedBoxBounds linkBounds = AxisAlignedBoxBounds.getEnclosing(getLinkBounds());
952 linkBounds.getTopLeft().add(getX() - LEFT_MARGIN, getY() + getLinkYOffset());
953 linkBounds.getSize().width += 2;
954 linkBounds.getSize().height += 2;
955 da.combineWith(linkBounds);
956 }
957
958 return da;
959
960 }
961
962 @Override
963 public void scale(Float scale, int originX, int originY) {
964 setScale(getScale() * scale);
965 super.scale(scale, originX, originY);
966 }
967
968 public void setFlipX(boolean flip) {
969 _flipX = flip;
970 }
971
972 public void setFlipY(boolean flip) {
973 _flipY = flip;
974 }
975
976 public boolean getFlipX() {
977 return _flipX;
978 }
979
980 public boolean getFlipY() {
981 return _flipY;
982 }
983
984 public void setRotate(double rotate) {
985 _rotate = rotate;
986 updateSource();
987 invalidateBounds();
988 }
989
990 public double getRotate() {
991 return _rotate;
992 }
993
994 public boolean MouseOverBackgroundPixel(int mouseX, int mouseY, Colour bg_col)
995 {
996 int base_x = (_anchorLeft!=null) ? _anchorLeft : _source.getX();
997 int base_y = (_anchorTop!=null) ? _anchorTop : _source.getY();
998 int x = mouseX - base_x;
999 int y = mouseY - base_y;
1000 int xInverseScale = Math.round(x / _scale);
1001 int yInverseScale = Math.round(y / _scale);
1002
1003 Colour c = _image.getPixel(xInverseScale, yInverseScale);
1004 int c_alpha = c.getAlpha255();
1005 if (c_alpha == 0) {
1006 return true;
1007 }
1008
1009 int c_red = c.getRed255();
1010 int c_green = c.getGreen255();
1011 int c_blue = c.getBlue255();
1012
1013 int bg_red = (bg_col!=null) ? bg_col.getRed255() : 0xff;
1014 int bg_green = (bg_col!=null) ? bg_col.getGreen255() : 0xff;
1015 int bg_blue = (bg_col!=null) ? bg_col.getBlue255() : 0xff;
1016
1017 int red_diff = Math.abs(c_red - bg_red);
1018 int green_diff = Math.abs(c_green - bg_green);
1019 int blue_diff = Math.abs(c_blue - bg_blue);
1020
1021 return ((red_diff<=2) && (green_diff<=2) && (blue_diff<=2));
1022
1023 }
1024}
Note: See TracBrowser for help on using the repository browser.