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    }