/** * Calculate the size of this IPTC directory if if were to be encoded inside a JPEG image. * * @return Returns the size in bytes. */ private int calcEncodedIPTCSize() { int size = 0; for (Iterator<Map.Entry<Integer, ImageMetaValue>> i = iterator(); i.hasNext(); ) { final Map.Entry<Integer, ImageMetaValue> me = i.next(); final ImageMetaValue imValue = me.getValue(); switch (imValue.getType()) { case META_SBYTE: case META_UBYTE: size += IPTC_ENTRY_HEADER_SIZE + 1; break; case META_DATE: size += IPTC_ENTRY_HEADER_SIZE + IPTC_DATE_SIZE; break; case META_SSHORT: case META_USHORT: size += IPTC_ENTRY_HEADER_SIZE + IPTC_SHORT_SIZE; break; case META_STRING: for (String s : imValue.getValues()) { try { final byte[] b = s.getBytes("ISO-8859-1"); size += IPTC_ENTRY_HEADER_SIZE + b.length; } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } break; default: throw new IllegalStateException(); } } return size; }
/** {@inheritDoc} */ public RenderedImage getPreviewImage(ImageInfo imageInfo, int maxWidth, int maxHeight) throws BadImageFileException, IOException, UnknownImageTypeException { final ImageMetadata metadata = imageInfo.getMetadata(); final ImageMetadataDirectory dir = metadata.getDirectoryFor(CIFFDirectory.class); if (dir != null) { final ImageMetaValue colorSpace = dir.getValue(CIFF_COLOR_SPACE); ColorSpace cs = JAIContext.sRGBColorSpace; if (colorSpace != null) switch (colorSpace.getIntValue()) { case CIFF_COLOR_SPACE_ADOBE_RGB: cs = JAIContext.adobeRGBColorSpace; break; } final RenderedImage image = JPEGImageType.getImageFromBuffer( imageInfo.getByteBuffer(), dir.getValue(CIFF_PREVIEW_IMAGE_OFFSET), 0, dir.getValue(CIFF_PREVIEW_IMAGE_LENGTH), cs, maxWidth, maxHeight); if (image != null) return image; } return super.getPreviewImage(imageInfo, maxWidth, maxHeight); }
/** * Puts a key/value pair into this directory. For a Olympus tag that has subfields, expands the * values into multiple key/value pairs. * * @param tagID The metadata tag ID (the key). * @param value The value to put. * @see #valueToString(ImageMetaValue) */ public void putValue(Integer tagID, ImageMetaValue value) { switch (tagID) { case OLYMPUS_CAMERA_SETTINGS: case OLYMPUS_CAMERA_SETTINGS_OLD: explodeSubfields(tagID, 0, value, true); break; case OLYMPUS_ISO: { final float n = value.getFloatValue(); value = new UnsignedShortMetaValue((int) (100 * Math.pow(2, n - 5))); break; } case OLYMPUS_WHITE_BALANCE_MODE: final long[] v = ((LongMetaValue) value).getLongValues(); final int n; switch (v.length) { case 1: n = (int) v[0]; break; case 2: n = (int) (v[0] * 10 + v[1]); break; default: return; } value = new UnsignedShortMetaValue(n); break; } super.putValue(tagID, value); }
/** * Merges the given {@link DateMetaValue} and a time into a new {@link DateMetaValue}. * * @param date The starting {@link DateMetaValue}. It is not modified. * @param timeTagID The ID of the tag of the {@link ImageMetaValue} that contains a time in the * form HHMMSS+HHMM. (This is the form of all IPTC time values.) * @return Returns a new {@link DateMetaValue} that is the date of the old {@link DateMetaValue} * plus the time. */ private ImageMetaValue mergeDateTime(ImageMetaValue date, int timeTagID) { final ImageMetaValue timeValue = getValue(timeTagID); if (timeValue == null) return date; final String timeString = timeValue.getStringValue(); if (timeString.length() != 11) return date; try { final int hh = Integer.parseInt(timeString.substring(0, 2)); final int mm = Integer.parseInt(timeString.substring(2, 4)); final int ss = Integer.parseInt(timeString.substring(4, 6)); int zh = Integer.parseInt(timeString.substring(7, 9)); int zm = Integer.parseInt(timeString.substring(9, 11)); // // We have to adjust the hour and minute by the timezone offset and // we need to do this based on GMT. // // For an offset like -08:00 (i.e., somewhere on the west coast of // the USA), we need to add 8 hours so midnight on the west coast // is 8am GMT. To get this result, we don't have to do anything to // the sign of zh and zm. // // For an offset like +08:00 (i.e., somewhere on the west coast of // Australia), we need to substract 8 hours. To get this result, // we negate zh and zm. // switch (timeString.charAt(6)) { case '+': zh = -zh; zm = -zm; break; case '-': break; default: return date; } final int delta = ((hh + zh) * 60 * 60 + (mm + zm) * 60 + ss) * 1000; final DateMetaValue newDateValue = (DateMetaValue) date.clone(); final Date newDate = newDateValue.getDateValue(); newDate.setTime(newDate.getTime() + delta); return newDateValue; } catch (NumberFormatException e) { return date; } }
/** {@inheritDoc} */ public String valueToString(ImageMetaValue value) { switch (value.getOwningTagID()) { case IPTC_DATE_CREATED: case IPTC_DATE_SENT: case IPTC_DIGITAL_CREATION_DATE: case IPTC_EXPIRATION_DATE: case IPTC_RELEASE_DATE: { // // IPTC dates don't have times (those are stored seperately -- // see below), so we chop off the "00:00:00" from the Date // value. // final String s = value.getStringValue(); if (s.length() != 19) // YYYY:MM:DD 00:00:00 break; if (!s.endsWith("00:00:00")) break; return s.substring(0, 11); } case IPTC_DIGITAL_CREATION_TIME: case IPTC_EXPIRATION_TIME: case IPTC_RELEASE_TIME: case IPTC_TIME_CREATED: case IPTC_TIME_SENT: { final String s = value.getStringValue(); if (s.length() != 11) // HHMMSS+HHMM break; return s.substring(0, 2) + ':' + // HH s.substring(2, 4) + ':' + // MM s.substring(4, 6) + ' ' + // SS s.substring(6); // GMT offset } } return super.valueToString(value); }
/** {@inheritDoc} */ public String valueToString(ImageMetaValue value) { switch (value.getOwningTagID()) { case OLYMPUS_BLUE_BALANCE: case OLYMPUS_RED_BALANCE: return TextUtil.tenths(value.getFloatValue() / 256); case OLYMPUS_CS_CUSTOM_SATURATION: { final String model = getOwningMetadata().getCameraMake(true); if (model != null && model.contains("E-1")) { final long[] n = ((LongMetaValue) value).getLongValues(); n[0] -= n[1]; n[2] -= n[1]; return n[0] + " (0," + n[2] + ')'; } // no break; } case OLYMPUS_CS_CONTRAST_SETTING: case OLYMPUS_CS_PM_CONTRAST: case OLYMPUS_CS_PM_SATURATION: case OLYMPUS_CS_PM_SHARPNESS: case OLYMPUS_CS_SHARPNESS_SETTING: { final String[] values = value.getValues(); if (values.length != 3) return null; return values[0] + " (" + values[1] + ',' + values[2] + ')'; } case OLYMPUS_CS_PANORAMA_MODE: { if (value.getValueCount() != 2) break; final int tagID = value.getOwningTagID(); return getTagValueLabelFor(tagID, value.getIntValue()) + ", " + value.getValues()[1]; } case OLYMPUS_FOCAL_PLANE_DIAGONAL: return value.getStringValue() + "mm"; // TODO: localize } return super.valueToString(value); }
/** {@inheritDoc} */ public String getTitle() { final ImageMetaValue value = getValue(IPTC_OBJECT_NAME); return value != null ? value.getStringValue() : null; }
/** {@inheritDoc} */ public String getCopyright() { final ImageMetaValue value = getValue(IPTC_COPYRIGHT_NOTICE); return value != null ? value.getStringValue() : null; }
/** {@inheritDoc} */ public String getArtist() { final ImageMetaValue value = getValue(IPTC_CREATOR); return value != null ? value.getStringValue() : null; }
/** * Encode an {@link IPTCDirectory}'s values into a {@link ByteBuffer} suitable for writing into a * JPEG image file. * * @param includePhotoshopHeader If <code>true</code>, encode the Photoshop header as is needed in * JPEG files. * @return Returns said {@link ByteBuffer}. */ private ByteBuffer encodeImpl(boolean includePhotoshopHeader) { final int encodedIPTCSize = calcEncodedIPTCSize(); int bufSize = encodedIPTCSize; ////////// Encode Photoshop IPTC header, if wanted //////////////////// if (includePhotoshopHeader) bufSize += IPTC_JPEG_HEADER_SIZE; // // Ensure the buffer size is padded to be an even number of bytes. // final ByteBuffer buf = ByteBuffer.allocate(bufSize + (bufSize & 1)); if (includePhotoshopHeader) { ByteBufferUtil.put(buf, PHOTOSHOP_3_IDENT, "ASCII"); buf.put((byte) 0); ByteBufferUtil.put(buf, PHOTOSHOP_CREATOR_CODE, "ASCII"); buf.putShort(PHOTOSHOP_IPTC_RESOURCE_ID); buf.putShort((short) 0); // resource name (empty) buf.putInt(encodedIPTCSize); } ////////// Now encode all the IPTC tags /////////////////////////////// final Integer[] tagIDs = getTagIDSet(false).toArray(new Integer[] {null}); Arrays.sort(tagIDs); for (int tagID : tagIDs) { if (!tagIsIIM(tagID)) { // // Non-IIM tags can't be encoded into binary form. // continue; } final ImageMetaValue imValue = getValue(tagID); switch (imValue.getType()) { case META_SBYTE: case META_UBYTE: { encodeTag(buf, tagID); buf.putShort((short) 1); buf.put(imValue.getStringValue().getBytes()[0]); break; } case META_DATE: { encodeTag(buf, tagID); String date = imValue.getStringValue(); date = date.substring(0, 4) + // YYYY date.substring(5, 7) + // MM date.substring(8, 10); // DD encodeString(buf, date); break; } case META_SSHORT: case META_USHORT: { encodeTag(buf, tagID); buf.putShort((short) 2); buf.putShort(imValue.getShortValue()); break; } case META_STRING: { for (String s : imValue.getValues()) { encodeTag(buf, tagID); encodeString(buf, s); } break; } default: throw new IllegalStateException(); } } if ((bufSize & 1) != 0) { // // The number of encoded bytes is odd: pad to make even. // buf.put((byte) 0); } return buf; }
/** {@inheritDoc} */ protected Collection<Element> toXMP(Document xmpDoc, String nsURI, String prefix) { Element rdfDescElement = null; // // First we have to scan through all the metadata values to see if at // least one of them is one of the Creator Contact Info values because // they need special handling. // Element cciElement = null; for (Iterator<Map.Entry<Integer, ImageMetaValue>> i = iterator(); i.hasNext(); ) { final Map.Entry<Integer, ImageMetaValue> me = i.next(); final int tagID = me.getKey(); if (tagIsCreatorContactInfo(tagID)) { rdfDescElement = XMPUtil.createRDFDescription(xmpDoc, nsURI, prefix); cciElement = XMLUtil.addElementChildTo(rdfDescElement, nsURI, prefix + ':' + "CreatorContactInfo"); cciElement.setAttribute(XMP_RDF_PREFIX + ":parseType", "Resource"); rdfDescElement.appendChild(cciElement); break; } } // // Now go through the metadata values for real and convert them to XMP. // If a given metadata value is one of the Creator Contact Info values, // make its parent the Creator Contact Info element rather than the // given parent. // for (Iterator<Map.Entry<Integer, ImageMetaValue>> i = iterator(); i.hasNext(); ) { final Map.Entry<Integer, ImageMetaValue> me = i.next(); final int tagID = me.getKey(); ImageMetaValue value = me.getValue(); // // Only these tags are encoded as part of IPTC for XMP Core. // switch (tagID) { case IPTC_COUNTRY_CODE: case IPTC_INTELLECTUAL_GENRE: case IPTC_LOCATION: case IPTC_SCENE: case IPTC_SUBJECT_CODE: break; default: if (!tagIsCreatorContactInfo(tagID)) continue; } /* // // In XMP, IPTC time fields are merged into the corresponding date // field. // switch ( tagID ) { case IPTC_DATE_CREATED: value = mergeDateTime( value, IPTC_TIME_CREATED ); break; case IPTC_DATE_SENT: value = mergeDateTime( value, IPTC_TIME_SENT ); break; case IPTC_DIGITAL_CREATION_DATE: value = mergeDateTime( value, IPTC_DIGITAL_CREATION_TIME ); break; case IPTC_EXPIRATION_DATE: value = mergeDateTime( value, IPTC_EXPIRATION_TIME ); break; case IPTC_RELEASE_DATE: value = mergeDateTime( value, IPTC_RELEASE_TIME ); break; case IPTC_DIGITAL_CREATION_TIME: case IPTC_EXPIRATION_TIME: case IPTC_RELEASE_TIME: case IPTC_TIME_CREATED: case IPTC_TIME_SENT: continue; } */ final Element valueElement = value.toXMP(xmpDoc, nsURI, prefix); if (valueElement != null) { final Element parent; if (tagIsCreatorContactInfo(tagID)) parent = cciElement; else { if (rdfDescElement == null) rdfDescElement = XMPUtil.createRDFDescription(xmpDoc, nsURI, prefix); parent = rdfDescElement; } //noinspection ConstantConditions parent.appendChild(valueElement); } } if (rdfDescElement != null) { final Collection<Element> elements = new ArrayList<Element>(1); elements.add(rdfDescElement); return elements; } return null; }