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