// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.are.viewer; import java.awt.Color; import java.awt.Image; import java.awt.Point; import java.nio.ByteBuffer; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.TextString; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.BasicAnimationProvider; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.Viewable; import org.infinity.resource.are.Animation; import org.infinity.resource.are.AreResource; import org.infinity.resource.are.viewer.icon.ViewerIcons; import org.infinity.resource.graphics.BamDecoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.io.FileManager; /** * Handles specific layer type: ARE/Background Animation */ public class LayerObjectAnimation extends LayerObject { private static final Image[][] Icon = new Image[][]{ // active versions {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_2)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_WBM_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_WBM_2)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_PVRZ_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_PVRZ_2)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_BAM_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_BAM_2)}, // inactive versions {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_1_BW), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_BAM_2_BW)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_WBM_1_BW), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_WBM_2_BW)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_PVRZ_1_BW), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_PVRZ_2_BW)}, {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_BAM_1_BW), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ANIM_BAM_2_BW)} }; private static Point Center = new Point(16, 17); private final Animation anim; private final Point location = new Point(); private final AbstractLayerItem[] items = new AbstractLayerItem[2]; private Flag scheduleFlags; public LayerObjectAnimation(AreResource parent, Animation anim) { super(ViewerConstants.RESOURCE_ARE, "Animation", Animation.class, parent); this.anim = anim; init(); } @Override public void close() { super.close(); // removing cached references for (int i = 0; i < items.length; i++) { if (items[i] != null) { Object key = items[i].getData(); if (key != null) { switch (i) { case ViewerConstants.ANIM_ITEM_ICON: SharedResourceCache.remove(SharedResourceCache.Type.ICON, key); break; case ViewerConstants.ANIM_ITEM_REAL: SharedResourceCache.remove(SharedResourceCache.Type.ANIMATION, key); break; } } } } } @Override public Viewable getViewable() { return anim; } @Override public Viewable[] getViewables() { return new Viewable[]{anim}; } @Override public AbstractLayerItem getLayerItem() { return items[0]; } /** * Returns the layer item of the specific state. (either ANIM_ITEM_ICON or ANIM_ITEM_REAL). * @param type The state of the item to be returned. * @return The desired layer item, or {@code null} if not available. */ @Override public AbstractLayerItem getLayerItem(int type) { type = (type == ViewerConstants.ANIM_ITEM_REAL) ? ViewerConstants.ANIM_ITEM_REAL : ViewerConstants.ANIM_ITEM_ICON; return items[type]; } @Override public AbstractLayerItem[] getLayerItems() { return items; } @Override public void reload() { init(); } @Override public void update(double zoomFactor) { for (int i = 0; i < items.length; i++) { if (items[i] != null) { items[i].setItemLocation((int)(location.x*zoomFactor + (zoomFactor / 2.0)), (int)(location.y*zoomFactor + (zoomFactor / 2.0))); if (i == ViewerConstants.ANIM_ITEM_REAL) { ((AnimatedLayerItem)items[i]).setZoomFactor(zoomFactor); } } } } @Override public Point getMapLocation() { return location; } @Override public Point[] getMapLocations() { return new Point[]{location, location}; } @Override public boolean isScheduled(int schedule) { if (schedule >= ViewerConstants.TIME_0 && schedule <= ViewerConstants.TIME_23) { return (scheduleFlags.isFlagSet(schedule)); } else { return false; } } /** * Sets the lighting condition of the animation. Does nothing if the animation is flagged as * self-illuminating. * @param dayTime One of the constants: {@code TilesetRenderer.LIGHTING_DAY}, * {@code TilesetRenderer.LIGHTING_TWILIGHT}, {@code TilesetRenderer.LIGHTING_NIGHT}. */ public void setLighting(int dayTime) { if (items[ViewerConstants.ANIM_ITEM_REAL] != null) { AnimatedLayerItem item = (AnimatedLayerItem)items[ViewerConstants.ANIM_ITEM_REAL]; if (item != null) { BasicAnimationProvider provider = item.getAnimation(); if (provider instanceof BackgroundAnimationProvider) { BackgroundAnimationProvider anim = (BackgroundAnimationProvider)provider; anim.setLighting(dayTime); } item.repaint(); } } } private void init() { if (anim != null) { String keyAnim = ""; String msg = ""; int iconIdx = 0; BackgroundAnimationProvider animation = null; int skippedFrames = 0; boolean isActive = true, isBlended = false, isMirrored = false, isSelfIlluminated = false; try { // PST seems to ignore a couple of animation settings boolean isTorment = (Profile.getEngine() == Profile.Engine.PST); boolean isEE = Profile.isEnhancedEdition(); location.x = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_LOCATION_X)).getValue(); location.y = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_LOCATION_Y)).getValue(); Flag flags = (Flag)anim.getAttribute(Animation.ARE_ANIMATION_APPEARANCE); isActive = flags.isFlagSet(0); isBlended = flags.isFlagSet(1) || isTorment; isMirrored = flags.isFlagSet(11); isSelfIlluminated = !flags.isFlagSet(2); boolean isSynchronized = flags.isFlagSet(4); boolean isWBM = false; boolean isPVRZ = false; if (isEE) { isWBM = flags.isFlagSet(13); isPVRZ = flags.isFlagSet(15); if (flags.isFlagSet(13)) { iconIdx = 1; } else if (flags.isFlagSet(15)) { iconIdx = 2; } else { iconIdx = 3; } } if (!isActive) { iconIdx += 4; // adjusting to display inactive versions of the icons } msg = ((TextString)anim.getAttribute(Animation.ARE_ANIMATION_NAME)).toString(); scheduleFlags = ((Flag)anim.getAttribute(Animation.ARE_ANIMATION_ACTIVE_AT)); int baseAlpha = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_TRANSLUCENCY)).getValue(); if (baseAlpha < 0) baseAlpha = 0; else if (baseAlpha > 255) baseAlpha = 255; baseAlpha = 255 - baseAlpha; // initializing frames if (isWBM) { // using icon as placeholder // generating key from icon hashcode keyAnim = String.format(String.format("%1$08x", Icon[iconIdx][0].hashCode())); BamDecoder bam = null; if (!SharedResourceCache.contains(SharedResourceCache.Type.ANIMATION, keyAnim)) { bam = new PseudoBamDecoder(ColorConvert.toBufferedImage(Icon[iconIdx][0], true, false), Center); SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim, new ResourceAnimation(keyAnim, bam)); } else { SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim); bam = ((ResourceAnimation)SharedResourceCache.get(SharedResourceCache.Type.ANIMATION, keyAnim)).getData(); } animation = new BackgroundAnimationProvider(bam); animation.setBaseAlpha(baseAlpha); animation.setActive(isActive); animation.setActiveIgnored(Settings.OverrideAnimVisibility); } else if (isPVRZ) { // using icon as placeholder // generating key from icon hashcode keyAnim = String.format(String.format("%1$08x", Icon[iconIdx][0].hashCode())); BamDecoder bam = null; if (!SharedResourceCache.contains(SharedResourceCache.Type.ANIMATION, keyAnim)) { bam = new PseudoBamDecoder(ColorConvert.toBufferedImage(Icon[iconIdx][0], true, false), Center); SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim, new ResourceAnimation(keyAnim, bam)); } else { SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim); bam = ((ResourceAnimation)SharedResourceCache.get(SharedResourceCache.Type.ANIMATION, keyAnim)).getData(); } animation = new BackgroundAnimationProvider(bam); animation.setBaseAlpha(baseAlpha); animation.setActive(isActive); animation.setActiveIgnored(Settings.OverrideAnimVisibility); } else { // setting up BAM frames String animFile = ((ResourceRef)anim.getAttribute(Animation.ARE_ANIMATION_RESREF)).getResourceName(); if (animFile == null || animFile.isEmpty() || "None".equalsIgnoreCase(animFile)) { animFile = ""; } boolean isPartial = flags.isFlagSet(3) && !isTorment; // boolean isRandom = flags.isFlagSet(5); boolean playAllFrames = flags.isFlagSet(9); // play all cycles simultaneously boolean hasExternalPalette = flags.isFlagSet(10); int cycle = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_ANIMATION_INDEX)).getValue(); int frameCount = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_FRAME_INDEX)).getValue(); skippedFrames = ((DecNumber)anim.getAttribute(Animation.ARE_ANIMATION_START_DELAY)).getValue(); if (isSynchronized || isTorment) { skippedFrames = 0; } // retrieving external palette (if available) int[] palette = null; String paletteFile = null; if (hasExternalPalette) { ResourceRef ref = (ResourceRef)anim.getAttribute(Animation.ARE_ANIMATION_PALETTE); if (ref != null) { paletteFile = ref.getResourceName(); if (paletteFile == null || paletteFile.isEmpty() || "None".equalsIgnoreCase(paletteFile)) { paletteFile = ""; } palette = getExternalPalette(paletteFile); } } // generating unique key from BAM filename and optional palette hashcode keyAnim = String.format("%1$s", animFile); BamDecoder bam = null; if (!SharedResourceCache.contains(SharedResourceCache.Type.ANIMATION, keyAnim)) { ResourceEntry bamEntry = ResourceFactory.getResourceEntry(animFile); bam = BamDecoder.loadBam(bamEntry); SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim, new ResourceAnimation(keyAnim, bam)); } else { SharedResourceCache.add(SharedResourceCache.Type.ANIMATION, keyAnim); bam = ((ResourceAnimation)SharedResourceCache.get(SharedResourceCache.Type.ANIMATION, keyAnim)).getData(); } animation = new BackgroundAnimationProvider(bam); animation.setPalette(palette); animation.setPaletteEnabled(palette != null); animation.setActive(isActive); animation.setActiveIgnored(Settings.OverrideAnimVisibility); animation.setBaseAlpha(baseAlpha); animation.setBlended(isBlended); BamDecoder.BamControl control = bam.createControl(); if (cycle < 0) cycle = 0; else if (cycle >= control.cycleCount()) cycle = control.cycleCount() - 1; animation.setCycle(cycle); animation.setLooping(!isPartial); animation.setMirrored(isMirrored); animation.setMultiPart(playAllFrames); animation.setSelfIlluminated(isSelfIlluminated); animation.setFrameCap(isPartial ? frameCount : -1); animation.setStartFrame(skippedFrames); } } catch (Exception e) { e.printStackTrace(); } // Using cached icons Image[] icon; String keyIcon = String.format("%1$s%2$s", SharedResourceCache.createKey(Icon[iconIdx][0]), SharedResourceCache.createKey(Icon[iconIdx][1])); if (SharedResourceCache.contains(SharedResourceCache.Type.ICON, keyIcon)) { icon = ((ResourceIcon)SharedResourceCache.get(SharedResourceCache.Type.ICON, keyIcon)).getData(); SharedResourceCache.add(SharedResourceCache.Type.ICON, keyIcon); } else { icon = Icon[iconIdx]; SharedResourceCache.add(SharedResourceCache.Type.ICON, keyIcon, new ResourceIcon(keyIcon, icon)); } IconLayerItem item1 = new IconLayerItem(location, anim, msg, icon[0], Center); item1.setData(keyIcon); item1.setName(getCategory()); item1.setToolTipText(msg); item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icon[1]); item1.setVisible(isVisible()); items[0] = item1; AnimatedLayerItem item2 = new AnimatedLayerItem(location, anim, msg, animation); item2.setData(keyAnim); item2.setName(getCategory()); item2.setToolTipText(msg); item2.setVisible(false); item2.setFrameRate(10.0); item2.setAutoPlay(false); item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, new Color(0xA0FF0000, true)); item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); item2.setFrameColor(AbstractLayerItem.ItemState.HIGHLIGHTED, Color.RED); item2.setFrameWidth(AbstractLayerItem.ItemState.HIGHLIGHTED, 2); item2.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); items[1] = item2; } } // Loads a palette from the specified BMP resource (only 8-bit standard BMPs supported) private int[] getExternalPalette(String bmpFile) { int[] retVal = null; if (bmpFile != null && !bmpFile.isEmpty()) { ResourceEntry entry = ResourceFactory.getResourceEntry(bmpFile); if (entry == null) { entry = new FileResourceEntry(FileManager.resolve(bmpFile)); } if (entry != null) { try { ByteBuffer buffer = entry.getResourceBuffer(); if (buffer != null && buffer.limit() > 1078) { boolean isBMP = (buffer.getShort(0) == 0x4D42); // 'BM' int palOfs = buffer.getInt(0x0e); int bpp = buffer.getShort(0x1c); if (isBMP && palOfs >= 0x28 && bpp == 8) { int ofs = 0x0e + palOfs; retVal = new int[256]; for (int i = 0; i < 256; i++) { retVal[i] = buffer.getInt(ofs + i*4); } } } buffer = null; } catch (Exception e) { } } entry = null; } if (retVal == null) { retVal = new int[0]; } return retVal; } }