/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License 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 it.geosolutions.jaiext.colorindexer; import java.awt.image.IndexColorModel; /** * A color indexer used when all we have is the target palette. Uses a LRU map to cache only the most recently used colors (the original image can * often have too many to practically keep in memory under concurrent load) * * @author Andrea Aime - GeoSolutions */ public class LRUColorIndexer implements ColorIndexer { IndexColorModel icm; ColorIndexer delegate; ColorMap cm; LRUColors lru; int maxSize; public LRUColorIndexer(IndexColorModel icm, int maxSize) { this.icm = icm; this.delegate = new SimpleColorIndexer(icm); this.cm = new ColorMap(maxSize); this.lru = new LRUColors(); this.maxSize = maxSize; } public IndexColorModel toIndexColorModel() { return icm; } public int getClosestIndex(int r, int g, int b, int a) { int idx = cm.get(r, g, b, a); if (idx == -1) { idx = delegate.getClosestIndex(r, g, b, a); cm.put(r, g, b, a, idx); if (cm.size() > maxSize) { ColorEntry ce = lru.removeLast(); int red = ColorUtils.red(ce.color); int green = ColorUtils.green(ce.color); int blue = ColorUtils.blue(ce.color); int alpha = ColorUtils.alpha(ce.color); cm.remove(red, green, blue, alpha); ce.color = ColorUtils.color(r, g, b, a); lru.add(ce); } else { int color = ColorUtils.color(r, g, b, a); lru.add(new ColorEntry(color, null, null)); } } return idx; } /** * ColorIndexer element used for storing an rgba index */ static final class ColorEntry { int color; ColorEntry previous; ColorEntry next; public ColorEntry(int color, ColorEntry previous, ColorEntry next) { this.color = color; this.previous = previous; this.next = next; } } /** * Class used for storing the {@link ColorEntry} instances using an LRU ordering */ static final class LRUColors { ColorEntry first; ColorEntry last; ColorEntry removeLast() { if (last == null) { return null; } // remove the last one ColorEntry result = last; last = result.previous; // if it was the only one, clean up the first too if (last == null) { first = null; } return result; } void touch(int color) { // easy case, no moving needed if (last == null || last == first) { return; } ColorEntry result = null; for (ColorEntry entry = first; entry != null; entry = entry.next) { if (entry.color == color) { result = entry; break; } } // are we moving the last one to first? if (result == last) { last = result.previous; result.previous.next = null; result.previous = null; result.next = first; first = result; } else if (result != first) { result.previous.next = result.next; result.previous = null; result.next = first; first = result; } } void add(ColorEntry ce) { if (first == null) { ce.next = null; ce.previous = null; first = last = ce; } else { ce.next = first; ce.next.previous = ce; ce.previous = null; first = ce; } } } }