You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
6.4 KiB
208 lines
6.4 KiB
package com.fr.start.fx; |
|
|
|
import com.sun.imageio.plugins.gif.GIFImageReader; |
|
import com.sun.imageio.plugins.gif.GIFImageReaderSpi; |
|
import com.sun.javafx.tk.ImageLoader; |
|
import com.sun.javafx.tk.PlatformImage; |
|
import javafx.animation.KeyFrame; |
|
import javafx.animation.Timeline; |
|
import javafx.event.Event; |
|
import javafx.event.EventHandler; |
|
import javafx.scene.image.WritableImage; |
|
import javafx.util.Duration; |
|
|
|
import javax.imageio.stream.FileImageInputStream; |
|
import java.io.File; |
|
import java.lang.ref.WeakReference; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.lang.reflect.Method; |
|
import java.net.MalformedURLException; |
|
import java.net.URI; |
|
import java.net.URL; |
|
import java.util.regex.Pattern; |
|
|
|
/** |
|
* 边加载边播放的gif加载器 |
|
* |
|
* @author daniel |
|
*/ |
|
public class FastGifImage extends WritableImage { |
|
private String url; |
|
private int gifCount; |
|
|
|
public FastGifImage(String url, int w, int h) { |
|
super(w, h); |
|
this.url = validateUrl(url); |
|
seekCount(); |
|
initialize(); |
|
} |
|
|
|
/** |
|
* 给出gif帧数,加快加载速度 |
|
* |
|
* @param url gif url |
|
* @param gifCount gif帧数 |
|
* @param w 宽 |
|
* @param h 高 |
|
*/ |
|
public FastGifImage(String url, int gifCount, int w, int h) { |
|
super(w, h); |
|
this.url = validateUrl(url); |
|
this.gifCount = gifCount; |
|
initialize(); |
|
} |
|
|
|
private void seekCount() { |
|
try { |
|
GIFImageReaderSpi spi = new GIFImageReaderSpi(); |
|
GIFImageReader gifReader = (GIFImageReader) spi.createReaderInstance(); |
|
gifReader.setInput(new FileImageInputStream(new File(new URI(url)))); |
|
gifCount = gifReader.getNumImages(true); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
private static final Pattern URL_QUICKMATCH = Pattern.compile("^\\p{Alpha}[\\p{Alnum}+.-]*:.*$"); |
|
|
|
private static String validateUrl(final String url) { |
|
if (url == null) { |
|
throw new NullPointerException("URL must not be null"); |
|
} |
|
|
|
if (url.trim().isEmpty()) { |
|
throw new IllegalArgumentException("URL must not be empty"); |
|
} |
|
|
|
try { |
|
if (!URL_QUICKMATCH.matcher(url).matches()) { |
|
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); |
|
URL resource; |
|
if (url.charAt(0) == '/') { |
|
resource = contextClassLoader.getResource(url.substring(1)); |
|
} else { |
|
resource = contextClassLoader.getResource(url); |
|
} |
|
if (resource == null) { |
|
throw new IllegalArgumentException("Invalid URL or resource not found"); |
|
} |
|
return resource.toString(); |
|
} |
|
// Use URL constructor for validation |
|
return new URL(url).toString(); |
|
} catch (final IllegalArgumentException e) { |
|
throw new IllegalArgumentException("Invalid URL" + e.getMessage()); |
|
} catch (final MalformedURLException e) { |
|
throw new IllegalArgumentException("Invalid URL" + e.getMessage()); |
|
} |
|
} |
|
|
|
private void finishImage(ImageLoader loader) { |
|
initializeAnimatedImage(loader); |
|
} |
|
|
|
// Generates the animation Timeline for multiframe images. |
|
private void initializeAnimatedImage(ImageLoader loader) { |
|
|
|
animation = new Animation(this, loader); |
|
animation.start(); |
|
} |
|
|
|
// Support for animated images. |
|
private Animation animation; |
|
|
|
private static final class Animation { |
|
final WeakReference<FastGifImage> imageRef; |
|
final Timeline timeline; |
|
private ImageLoader loader; |
|
|
|
public Animation(final FastGifImage image, final ImageLoader loader) { |
|
this.loader = loader; |
|
imageRef = new WeakReference<FastGifImage>(image); |
|
timeline = new Timeline(); |
|
timeline.setCycleCount(Timeline.INDEFINITE); |
|
|
|
final int frameCount = loader.getFrameCount(); |
|
int duration = 0; |
|
|
|
for (int i = 0; i < frameCount; ++i) { |
|
addKeyFrame(i, duration); |
|
duration = duration + loader.getFrameDelay(i); |
|
} |
|
|
|
// Note: we need one extra frame in the timeline to define how long |
|
// the last frame is shown, the wrap around is "instantaneous" |
|
addKeyFrame(0, duration); |
|
} |
|
|
|
public void start() { |
|
timeline.play(); |
|
} |
|
|
|
public void stop() { |
|
timeline.stop(); |
|
loader = null; |
|
} |
|
|
|
private void updateImage(final int frameIndex) { |
|
final FastGifImage image = imageRef.get(); |
|
if (image != null) { |
|
image.setPlatformImagePropertyImpl( |
|
loader.getFrame(frameIndex)); |
|
} else { |
|
timeline.stop(); |
|
} |
|
} |
|
|
|
private void addKeyFrame(final int index, final double duration) { |
|
timeline.getKeyFrames().add( |
|
new KeyFrame(Duration.millis(duration), |
|
new EventHandler() { |
|
@Override |
|
public void handle(Event event) { |
|
updateImage(index); |
|
} |
|
} |
|
)); |
|
} |
|
} |
|
|
|
private static Method method; |
|
|
|
static { |
|
try { |
|
method = FastGifImage.class.getSuperclass().getSuperclass().getDeclaredMethod("platformImagePropertyImpl"); |
|
method.setAccessible(true); |
|
} catch (Exception e) { |
|
|
|
} |
|
} |
|
|
|
private void setPlatformImagePropertyImpl(PlatformImage image) { |
|
try { |
|
Object o = method.invoke(this); |
|
Method method = o.getClass().getDeclaredMethod("set", Object.class); |
|
method.setAccessible(true); |
|
method.invoke(o, image); |
|
} catch (IllegalAccessException e) { |
|
e.printStackTrace(); |
|
} catch (InvocationTargetException e) { |
|
e.printStackTrace(); |
|
} catch (NoSuchMethodException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
|
|
private void initialize() { |
|
finishImage(new PrismImageLoader2(url, gifCount, (int) getRequestedWidth(), (int) getRequestedHeight(), isPreserveRatio(), isSmooth())); |
|
} |
|
|
|
/** |
|
* 销毁gif动画 |
|
*/ |
|
public void destroy() { |
|
animation.stop(); |
|
} |
|
|
|
}
|
|
|