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}