001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.progmodel.facets.value.image; 021 022import java.awt.Image; 023import java.awt.image.ColorModel; 024import java.awt.image.ImageObserver; 025import java.awt.image.MemoryImageSource; 026import java.awt.image.PixelGrabber; 027 028import org.apache.isis.applib.adapters.Parser; 029import org.apache.isis.applib.profiles.Localization; 030import org.apache.isis.core.commons.config.IsisConfiguration; 031import org.apache.isis.core.commons.exceptions.IsisException; 032import org.apache.isis.core.commons.exceptions.UnexpectedCallException; 033import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 034import org.apache.isis.core.metamodel.facetapi.Facet; 035import org.apache.isis.core.metamodel.facetapi.FacetHolder; 036import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderAndFacetAbstract; 037import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderContext; 038 039public abstract class ImageValueSemanticsProviderAbstract<T> extends ValueSemanticsProviderAndFacetAbstract<T> implements ImageValueFacet { 040 041 private static final int TYPICAL_LENGTH = 18; 042 private static final Object DEFAULT_VALUE = null; // no default 043 044 private static final char[] BASE_64_CHARS = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 045 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; 046 047 protected static final byte[] REVERSE_BASE_64_CHARS = new byte[0x100]; 048 049 static { 050 for (int i = 0; i < REVERSE_BASE_64_CHARS.length; i++) { 051 REVERSE_BASE_64_CHARS[i] = -1; 052 } 053 for (byte i = 0; i < BASE_64_CHARS.length; i++) { 054 REVERSE_BASE_64_CHARS[BASE_64_CHARS[i]] = i; 055 } 056 } 057 058 private FacetHolder facetHolder; 059 060 @SuppressWarnings("unchecked") 061 public ImageValueSemanticsProviderAbstract(final FacetHolder holder, final Class<T> adaptedClass, final IsisConfiguration configuration, final ValueSemanticsProviderContext context) { 062 super(ImageValueFacet.class, holder, adaptedClass, TYPICAL_LENGTH, Immutability.NOT_IMMUTABLE, EqualByContent.NOT_HONOURED, (T) DEFAULT_VALUE, configuration, context); 063 } 064 065 /** 066 * Returns null to indicate that this value does not parse entry strings 067 */ 068 @Override 069 public Parser<T> getParser() { 070 return null; 071 } 072 073 @Override 074 protected T doParse(final Object original, final String entry) { 075 throw new UnexpectedCallException(); 076 } 077 078 public byte[] getAsByteArray(final ObjectAdapter object) { 079 final int[] flatIntArray = flatten(object); 080 final byte[] byteArray = new byte[flatIntArray.length * 4]; 081 for (int i = 0; i < flatIntArray.length; i++) { 082 final int value = flatIntArray[i]; 083 byteArray[i * 4] = (byte) ((value >> 24) & 0xff); 084 byteArray[i * 4 + 1] = (byte) ((value >> 16) & 0xff); 085 byteArray[i * 4 + 2] = (byte) ((value >> 8) & 0xff); 086 byteArray[i * 4 + 3] = (byte) (value & 0xff); 087 } 088 return byteArray; 089 } 090 091 private static int byteArrayToInt(final byte[] byteArray, final int offset) { 092 int value = 0; 093 for (int i = 0; i < 4; i++) { 094 final int shift = (4 - 1 - i) * 8; 095 value += (byteArray[i + offset] & 0x000000FF) << shift; 096 } 097 return value; 098 } 099 100 @Override 101 public boolean alwaysReplace() { 102 return false; 103 } 104 105 @Override 106 public Facet getUnderlyingFacet() { 107 return null; 108 } 109 110 /** 111 * Not required because {@link #alwaysReplace()} is <tt>false</tt>. 112 */ 113 @Override 114 public void setUnderlyingFacet(final Facet underlyingFacet) { 115 throw new UnsupportedOperationException(); 116 } 117 118 @Override 119 public boolean isDerived() { 120 return false; 121 } 122 123 public T restoreFromByteArray(final byte[] byteArray) { 124 final int[] flatIntArray = new int[byteArray.length / 4]; 125 for (int i = 0; i < flatIntArray.length; i++) { 126 flatIntArray[i] = byteArrayToInt(byteArray, i * 4); 127 } 128 return setPixels(inflate(flatIntArray)); 129 } 130 131 private int[] flatten(final ObjectAdapter object) { 132 final int[][] image = getPixels(object); 133 final int[] flatArray = new int[(getHeight(object) * getWidth(object)) + 2]; 134 int flatIndex = 0; 135 flatArray[flatIndex++] = getHeight(object); 136 flatArray[flatIndex++] = getWidth(object); 137 for (int i = 0; i < getHeight(object); i++) { 138 for (int j = 0; j < getWidth(object); j++) { 139 flatArray[flatIndex++] = image[i][j]; 140 } 141 } 142 return flatArray; 143 } 144 145 private static int[][] inflate(final int[] flatIntArray) { 146 int flatIndex = 0; 147 final int height = flatIntArray[flatIndex++]; 148 final int width = flatIntArray[flatIndex++]; 149 150 final int[][] newImage = new int[height][width]; 151 152 for (int i = 0; i < height; i++) { 153 for (int j = 0; j < width; j++) { 154 newImage[i][j] = flatIntArray[flatIndex++]; 155 } 156 } 157 return newImage; 158 } 159 160 @Override 161 protected String doEncode(final Object object) { 162 final int[][] image = getPixels(object); 163 final int lines = image.length; 164 final int width = image[0].length; 165 final StringBuffer encodeData = new StringBuffer(lines * width * 4); 166 encodePixel(lines, encodeData); 167 encodePixel(width, encodeData); 168 for (int line = 0; line < lines; line++) { 169 for (int pixel = 0; pixel < width; pixel++) { 170 encodePixel(image[line][pixel], encodeData); 171 } 172 } 173 return encodeData.toString(); 174 } 175 176 protected Image createImage(final int[][] pixels) { 177 final int lines = pixels.length; 178 final int width = pixels[0].length; 179 final int[] pix = new int[lines * width]; 180 int offset = 0; 181 for (int line = 0; line < lines; line++) { 182 System.arraycopy(pixels[line], 0, pix, offset, width); 183 offset += width; 184 } 185 final MemoryImageSource source = new MemoryImageSource(width, lines, pix, 0, width); 186 return java.awt.Toolkit.getDefaultToolkit().createImage(source); 187 } 188 189 private int decodePixel(final String data, final int item) { 190 final int offset = item * 4; 191 int pixel = 0; 192 for (int i = 0; i < 4; i++) { 193 final char c = data.charAt(offset + i); 194 final byte b = REVERSE_BASE_64_CHARS[c]; 195 if (i == 0 && b >= 32) { 196 pixel = 0xff; 197 } 198 pixel = (pixel << 6) + b; 199 } 200 return pixel; 201 } 202 203 private void encodePixel(final int pixel, final StringBuffer encodeData) { 204 // TODO the encoding is poor as the negative numbers are not dealt with 205 // properly because the 6 MSBs 206 // are not included. 207 if (pixel > 0x7FFFFF || pixel < -0x7FFFFF) { 208 // throw new IsisException("" + pixel); 209 } 210 for (int i = 3; i >= 0; i--) { 211 final int bitsToShift = i * 6; 212 final int c = pixel >> bitsToShift; 213 final char cc = BASE_64_CHARS[c & 0x3F]; 214 encodeData.append(cc); 215 } 216 } 217 218 public String getIconName() { 219 return null; 220 } 221 222 protected abstract int[][] getPixels(Object object); 223 224 protected int[][] grabPixels(final Image image) { 225 final int width = image.getWidth(null); 226 final int lines = image.getHeight(null); 227 final int pixels[] = new int[width * lines]; 228 final PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, lines, pixels, 0, width); 229 grabber.setColorModel(ColorModel.getRGBdefault()); 230 try { 231 if (grabber.grabPixels() && (grabber.status() & ImageObserver.ALLBITS) != 0) { 232 final int[][] array = new int[lines][width]; 233 int srcPos = 0; 234 for (int line = 0; line < lines; line++) { 235 array[line] = new int[width]; 236 System.arraycopy(pixels, srcPos, array[line], 0, width); 237 for (int pixel = 0; pixel < array[line].length; pixel++) { 238 array[line][pixel] = array[line][pixel] | 0xFF000000; 239 } 240 srcPos += width; 241 } 242 return array; 243 } 244 // TODO what happens if the grab fails? 245 return new int[lines][width]; 246 } catch (final InterruptedException e) { 247 throw new IsisException(e); 248 } 249 } 250 251 @Override 252 public T doRestore(final String data) { 253 final int lines = decodePixel(data, 0); 254 final int width = decodePixel(data, 1); 255 final int[][] imageData = new int[lines][width]; 256 int offset = 2; 257 for (int line = 0; line < lines; line++) { 258 for (int pixel = 0; pixel < width; pixel++) { 259 imageData[line][pixel] = decodePixel(data, offset++); 260 imageData[line][pixel] = imageData[line][pixel] | 0xFF000000; 261 } 262 } 263 final T image = setPixels(imageData); 264 return image; 265 } 266 267 protected abstract T setPixels(int[][] pixels); 268 269 public void setMask(final String mask) { 270 } 271 272 @Override 273 public String titleString(final Object value, final Localization localization) { 274 return "image"; 275 } 276 277 @Override 278 public String titleStringWithMask(final Object value, final String usingMask) { 279 return "image"; 280 } 281 282 @Override 283 public FacetHolder getFacetHolder() { 284 return facetHolder; 285 } 286 287 @Override 288 public void setFacetHolder(final FacetHolder facetHolder) { 289 this.facetHolder = facetHolder; 290 } 291 292 // /////// toString /////// 293 294 @Override 295 public String toString() { 296 return "ImageValueSemanticsProvider"; 297 } 298 299}