/*
* Copyright (c) 2014, Michael Grossmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the jo-widgets.org nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.jowidgets.examples.common.image;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.jowidgets.api.animation.AnimationRunner;
import org.jowidgets.api.animation.IAnimationRunner;
import org.jowidgets.api.color.Colors;
import org.jowidgets.api.controller.IDisposeListener;
import org.jowidgets.api.controller.IPaintEvent;
import org.jowidgets.api.graphics.IGraphicContext;
import org.jowidgets.api.graphics.IPaintListener;
import org.jowidgets.api.image.IBufferedImage;
import org.jowidgets.api.image.IImage;
import org.jowidgets.api.toolkit.Toolkit;
import org.jowidgets.api.widgets.ICanvas;
import org.jowidgets.api.widgets.ICheckBox;
import org.jowidgets.api.widgets.IContainer;
import org.jowidgets.api.widgets.IFrame;
import org.jowidgets.api.widgets.IScrollComposite;
import org.jowidgets.api.widgets.ISlider;
import org.jowidgets.api.widgets.blueprint.ISliderBluePrint;
import org.jowidgets.common.application.IApplication;
import org.jowidgets.common.application.IApplicationLifecycle;
import org.jowidgets.common.color.ColorValue;
import org.jowidgets.common.types.Dimension;
import org.jowidgets.common.widgets.controller.IInputListener;
import org.jowidgets.common.widgets.layout.MigLayoutDescriptor;
import org.jowidgets.tools.layout.MigLayoutFactory;
import org.jowidgets.tools.widgets.blueprint.BPF;
import org.jowidgets.util.ICallback;
import org.jowidgets.util.NullCompatibleEquivalence;
import org.jowidgets.util.Tuple;
import org.jowidgets.util.ValueHolder;
import org.jowidgets.util.io.IoUtils;
public final class ImageAnimationDemo1 implements IApplication {
private static final Random RANDOM = new Random();
private static final String ENVELOPE_1_RESOURCE_NAME = "envelopes/envelope1.tupleArray";
private static final String ENVELOPE_2_RESOURCE_NAME = "envelopes/envelope2.tupleArray";
private static final int IMAGE_HEIGHT = 120;
private static final int IMAGE_WIDTH = 28699;
private static final int BORDER_SIZE = 5;
private static final int CANVAS_COUNT = 3;
private static final int SLIDER_MAX = 10000;
private static final int DEFAULT_ANIMATION_STEP_SIZE = 1;
private static final int DEFAULT_ANIMATION_DELAY = 20;
@Override
public void start(final IApplicationLifecycle lifecycle) {
//create containers
final ValueHolder<Boolean> changed = new ValueHolder<Boolean>(false);
final ValueHolder<Double> scaleFactor = new ValueHolder<Double>(1.0d);
final ValueHolder<Integer> offset = new ValueHolder<Integer>(0);
final ValueHolder<Integer> stepSize = new ValueHolder<Integer>(DEFAULT_ANIMATION_STEP_SIZE);
final ValueHolder<Integer> animationDelay = new ValueHolder<Integer>(DEFAULT_ANIMATION_DELAY);
//Create root frame
final IFrame rootFrame = Toolkit.createRootFrame(
BPF.frame("Image animation demo 1").setMinPackSize(new Dimension(1024, 200)),
lifecycle);
rootFrame.setLayout(MigLayoutFactory.growingInnerCellLayout());
//create scroll composite
final IScrollComposite container = rootFrame.add(BPF.scrollComposite(), MigLayoutFactory.GROWING_CELL_CONSTRAINTS);
container.setLayout(new MigLayoutDescriptor("wrap", "[grow, 0::]", "[120!]" + createCanvasRowLayout()));
//Create control's container
final IContainer controlContainer = container.add(BPF.composite(), "growx, growy, w 0::, h 0::");
controlContainer.setLayout(new MigLayoutDescriptor("wrap", "[]14[]14[]14[]14[]", "0[]0[grow, 0::]0"));
//Create controls header
controlContainer.add(BPF.textLabel("Scale"));
final ICheckBox autoOffsetCb = controlContainer.add(BPF.checkBox().setText("Offset (auto)"), "aligny b");
autoOffsetCb.setSelected(false);
controlContainer.add(BPF.textLabel("Step size"));
controlContainer.add(BPF.textLabel("Animation delay"));
final ICheckBox scrollingCb = controlContainer.add(BPF.checkBox().setText("Scrolling"), "aligny b");
scrollingCb.setSelected(false);
//Create scale slider
final ISliderBluePrint scaleSliderBp = BPF.slider().setMinimum(0).setMaximum(SLIDER_MAX).setVertical();
scaleSliderBp.setTickSpacing(SLIDER_MAX / 10);
final ISlider scaleSlider = controlContainer.add(scaleSliderBp, "growy, h 0::");
scaleSlider.setSelection(0);
scaleSlider.addInputListener(new IInputListener() {
@Override
public void inputChanged() {
final int value = scaleSlider.getSelection();
scaleFactor.set(new Double(0.1 + (double) (value) / (SLIDER_MAX / 20)));
//CHECKSTYLE:OFF
System.out.println(scaleFactor.get());
//CHECKSTYLE:ON
changed.set(true);
}
});
//Create offset slider
final ISliderBluePrint offsetSliderBp = BPF.slider().setMinimum(0).setMaximum(SLIDER_MAX).setVertical();
offsetSliderBp.setTickSpacing(SLIDER_MAX / 10);
final ISlider offsetSlider = controlContainer.add(offsetSliderBp, "growy, h 0::");
offsetSlider.setSelection(0);
offsetSlider.addInputListener(new IInputListener() {
@Override
public void inputChanged() {
final int value = offsetSlider.getSelection();
offset.set(new Integer(value / 10));
changed.set(true);
}
});
//Create step size slider
final ISliderBluePrint stepSizeSliderBp = BPF.slider().setMinimum(0).setMaximum(SLIDER_MAX).setVertical();
stepSizeSliderBp.setTickSpacing(SLIDER_MAX / 10);
final ISlider stepSizeSlider = controlContainer.add(stepSizeSliderBp, "growy, h 0::");
stepSizeSlider.setSelection(1);
stepSizeSlider.addInputListener(new IInputListener() {
@Override
public void inputChanged() {
final int value = stepSizeSlider.getSelection();
stepSize.set(new Integer(1 + (value / 100)));
//CHECKSTYLE:OFF
System.out.println(stepSize.get());
//CHECKSTYLE:ON
}
});
//Create animation delay slider
final ISliderBluePrint animationDelaySliderBp = BPF.slider().setMinimum(0).setMaximum(SLIDER_MAX).setVertical();
animationDelaySliderBp.setTickSpacing(SLIDER_MAX / 10);
final ISlider animationDelaySlider = controlContainer.add(animationDelaySliderBp, "growy, h 0::");
animationDelaySlider.setSelection(1);
animationDelaySlider.addInputListener(new IInputListener() {
@Override
public void inputChanged() {
final int value = animationDelaySlider.getSelection();
animationDelay.set(new Integer(20 + (value / 50)));
//CHECKSTYLE:OFF
System.out.println(animationDelay.get());
//CHECKSTYLE:ON
}
});
//create canvas list
final List<ICanvas> canvasList = new ArrayList<ICanvas>(CANVAS_COUNT);
for (int i = 0; i < CANVAS_COUNT - 2; i++) {
canvasList.add(createCanvas(container, createImage(null), scaleFactor, offset, scrollingCb, false));
}
if (CANVAS_COUNT > 1) {
canvasList.add(createCanvas(container, createImage(ENVELOPE_2_RESOURCE_NAME), scaleFactor, offset, scrollingCb, false));
}
if (CANVAS_COUNT > 0) {
canvasList.add(createCanvas(container, createImage(ENVELOPE_1_RESOURCE_NAME), scaleFactor, offset, scrollingCb, true));
}
rootFrame.setVisible(true);
final Animation animation = new Animation(
offsetSlider,
autoOffsetCb,
canvasList,
stepSize,
animationDelay,
scrollingCb,
changed);
animation.start();
rootFrame.addDisposeListener(new IDisposeListener() {
@Override
public void onDispose() {
animation.stop();
}
});
}
private static String createCanvasRowLayout() {
final StringBuilder result = new StringBuilder();
for (int i = 0; i < CANVAS_COUNT; i++) {
result.append("[]0");
}
return result.substring(0, result.lastIndexOf("0"));
}
private ICanvas createCanvas(
final IContainer parent,
final IImage image,
final ValueHolder<Double> scaleFactor,
final ValueHolder<Integer> offset,
final ICheckBox scrollingCb,
final boolean buttomBorder) {
final ICanvas result = parent.add(BPF.canvas(), "growx, w 0::, h " + IMAGE_HEIGHT + "!");
result.addPaintListener(new IPaintListener() {
private Integer lastOffset = null;
private Double lastScaleFactor = null;
@Override
public void paint(final IPaintEvent paintEvent) {
final IGraphicContext gc = paintEvent.getGraphicContext();
if (!scrollingCb.isSelected()
|| !NullCompatibleEquivalence.equals(scaleFactor.get(), lastScaleFactor)
|| !NullCompatibleEquivalence.equals(offset.get(), lastOffset)) {
final long currentTimeMillis = System.currentTimeMillis();
final int width = gc.getBounds().getWidth();
final int height = gc.getBounds().getHeight();
gc.clearRectangle(0, 0, width, height);
//draw the envelope image
try {
final Dimension size = image.getSize();
final Double currentScaleFactor = scaleFactor.get();
final Integer currentOffset = offset.get();
gc.drawImage(
image,
0,
0,
size.getWidth(),
size.getHeight(),
currentOffset,
0,
(int) (size.getWidth() / currentScaleFactor),
height);
}
catch (final Exception e) {
//TODO ignore for now
}
//draw the borders
gc.setForegroundColor(Colors.DISABLED);
gc.fillRectangle(0, 0, 1, height);
gc.fillRectangle(0, 0, width - 1, 1);
gc.fillRectangle(width - 1, 0, 1, height);
if (buttomBorder) {
gc.fillRectangle(0, height - 1, width - 1, 1);
}
//draw the marker
gc.setForegroundColor(Colors.ERROR);
gc.fillRectangle(width / 2, 0, 2, height);
lastOffset = offset.get();
lastScaleFactor = scaleFactor.get();
//CHECKSTYLE:OFF
System.out.println("Render time millis: " + (System.currentTimeMillis() - currentTimeMillis));
//CHECKSTYLE:ON
}
}
});
return result;
}
private IImage createImage(final String resourceName) {
final IBufferedImage image = Toolkit.getImageFactory().createBufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT);
final IGraphicContext gc = image.getGraphicContext();
gc.setBackgroundColor(Colors.WHITE);
gc.clearRectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
gc.setForegroundColor(new ColorValue(0, 0, 98));
final int innerHeight = IMAGE_HEIGHT - (2 * BORDER_SIZE);
final int baseLine = IMAGE_HEIGHT - BORDER_SIZE;
final double scale = innerHeight / 2;
double minValue = -1.0d;
double maxValue = 1.0d;
try {
ObjectInputStream ois = null;
if (resourceName != null) {
final InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resourceName);
ois = new ObjectInputStream(inputStream);
}
for (int i = 0; i < IMAGE_WIDTH; i++) {
if (ois != null) {
try {
@SuppressWarnings("unchecked")
final Tuple<Double, Double> tuple = (Tuple<Double, Double>) ois.readObject();
minValue = tuple.getFirst();
maxValue = tuple.getSecond();
}
catch (final Exception e) {
IoUtils.tryCloseSilent(ois);
ois = null;
}
}
else {
minValue = -1.0d * RANDOM.nextDouble();
maxValue = RANDOM.nextDouble();
}
final int maxY = (int) (baseLine - ((maxValue + 1.0) * scale));
final int minY = (int) (baseLine - ((minValue + 1.0) * scale));
if (minY != maxY) {
gc.fillRectangle(i, maxY, 1, Math.abs(maxY - minY));
}
else {
gc.drawPoint(i, minY);
}
}
IoUtils.tryCloseSilent(ois);
}
catch (final Exception e) {
throw new RuntimeException(e);
}
return image;
}
private final class Animation implements Runnable, ICallback<Void> {
private final List<ICanvas> canvasList;
private final ValueHolder<Integer> stepSize;
private final ValueHolder<Integer> animationDelay;
private final ValueHolder<Boolean> changed;
private final ISlider offsetSlider;
private final ICheckBox scrollingCb;
private final ICheckBox autoOffsetCb;
private final IAnimationRunner animationRunner;
private int animationStep;
private Animation(
final ISlider offsetSlider,
final ICheckBox autoOffsetCb,
final List<ICanvas> canvasList,
final ValueHolder<Integer> stepSize,
final ValueHolder<Integer> animationDelay,
final ICheckBox scrollingCb,
final ValueHolder<Boolean> changed) {
this.offsetSlider = offsetSlider;
this.autoOffsetCb = autoOffsetCb;
this.scrollingCb = scrollingCb;
this.canvasList = canvasList;
this.stepSize = stepSize;
this.animationDelay = animationDelay;
this.changed = changed;
this.animationStep = DEFAULT_ANIMATION_STEP_SIZE;
this.animationRunner = AnimationRunner.builder().setDelay(DEFAULT_ANIMATION_DELAY).build();
animationRunner.run(this, this);
}
public void stop() {
animationRunner.stop();
}
private void start() {
animationRunner.start();
}
@Override
public void run() {
animationStep = stepSize.get();
animationRunner.setDelay(animationDelay.get());
if (autoOffsetCb.isSelected()) {
final int selection = offsetSlider.getSelection();
if (selection + animationStep > SLIDER_MAX) {
offsetSlider.setSelection(0);
}
else {
offsetSlider.setSelection(selection + animationStep);
}
}
if (changed.get()) {
for (final ICanvas canvas : canvasList) {
canvas.redraw();
}
changed.set(false);
}
if (scrollingCb.isSelected()) {
final long currentTime = System.currentTimeMillis();
for (final ICanvas canvas : canvasList) {
final Dimension size = canvas.getSize();
canvas.scroll(0, 0, size.getWidth() - animationStep, size.getHeight(), animationStep, 0);
}
//CHECKSTYLE:OFF
System.out.println("SCROLL TIME: " + (System.currentTimeMillis() - currentTime));
//CHECKSTYLE:ON
}
}
@Override
public void call(final Void parameter) {
animationRunner.run(this, this);
}
}
}