001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.tiff; 018 019import java.io.IOException; 020import java.io.PrintWriter; 021import java.io.StringWriter; 022import java.nio.ByteOrder; 023import java.text.DateFormat; 024import java.text.SimpleDateFormat; 025import java.util.Arrays; 026import java.util.Date; 027import java.util.Locale; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031import org.apache.commons.imaging.ImagingException; 032import org.apache.commons.imaging.common.Allocator; 033import org.apache.commons.imaging.common.BinaryFunctions; 034import org.apache.commons.imaging.formats.tiff.constants.TiffConstants; 035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 036import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType; 037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo; 038 039/** 040 * A TIFF field in a TIFF directory. Immutable. 041 */ 042public class TiffField { 043 044 public final class OversizeValueElement extends AbstractTiffElement { 045 public OversizeValueElement(final int offset, final int length) { 046 super(offset, length); 047 } 048 049 @Override 050 public String getElementDescription() { 051 return "OversizeValueElement, tag: " + getTagInfo().name + ", fieldType: " + getFieldType().getName(); 052 } 053 } 054 055 private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName()); 056 private final TagInfo tagInfo; 057 private final int tag; 058 private final int directoryType; 059 private final AbstractFieldType abstractFieldType; 060 private final long count; 061 private final long offset; 062 private final byte[] value; 063 private final ByteOrder byteOrder; 064 065 private final int sortHint; 066 067 public TiffField(final int tag, final int directoryType, final AbstractFieldType abstractFieldType, final long count, final long offset, final byte[] value, 068 final ByteOrder byteOrder, final int sortHint) { 069 070 this.tag = tag; 071 this.directoryType = directoryType; 072 this.abstractFieldType = abstractFieldType; 073 this.count = count; 074 this.offset = offset; 075 this.value = value; 076 this.byteOrder = byteOrder; 077 this.sortHint = sortHint; 078 079 tagInfo = TiffTags.getTag(directoryType, tag); 080 } 081 082 public void dump() { 083 try (StringWriter sw = new StringWriter(); 084 PrintWriter pw = new PrintWriter(sw)) { 085 dump(pw); 086 pw.flush(); 087 sw.flush(); 088 LOGGER.fine(sw.toString()); 089 } catch (final IOException e) { 090 LOGGER.log(Level.SEVERE, e.getMessage(), e); 091 } 092 } 093 094 public void dump(final PrintWriter pw) { 095 dump(pw, null); 096 } 097 098 public void dump(final PrintWriter pw, final String prefix) { 099 if (prefix != null) { 100 pw.print(prefix + ": "); 101 } 102 103 pw.println(toString()); 104 pw.flush(); 105 } 106 107 /** 108 * Returns a copy of the raw value of the field. 109 * 110 * @return the value of the field, in the byte order of the field. 111 */ 112 public byte[] getByteArrayValue() { 113 return BinaryFunctions.copyOfStart(value, getBytesLength()); 114 } 115 116 /** 117 * Returns the field's byte order. 118 * 119 * @return the byte order 120 */ 121 public ByteOrder getByteOrder() { 122 return byteOrder; 123 } 124 125 /** 126 * The length of the field's value. 127 * 128 * @return the length, in bytes. 129 */ 130 public int getBytesLength() { 131 return (int) count * abstractFieldType.getSize(); 132 } 133 134 /** 135 * Returns the field's count, derived from bytes 4-7. 136 * 137 * @return the count 138 */ 139 public long getCount() { 140 return count; 141 } 142 143 public String getDescriptionWithoutValue() { 144 return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): "; 145 } 146 147 public int getDirectoryType() { 148 return directoryType; 149 } 150 151 public double[] getDoubleArrayValue() throws ImagingException { 152 final Object o = getValue(); 153 // if (o == null) 154 // return null; 155 156 if (o instanceof Number) { 157 return new double[] { ((Number) o).doubleValue() }; 158 } 159 if (o instanceof Number[]) { 160 final Number[] numbers = (Number[]) o; 161 final double[] result = Allocator.doubleArray(numbers.length); 162 Arrays.setAll(result, i -> numbers[i].doubleValue()); 163 return result; 164 } 165 if (o instanceof short[]) { 166 final short[] numbers = (short[]) o; 167 final double[] result = Allocator.doubleArray(numbers.length); 168 Arrays.setAll(result, i -> numbers[i]); 169 return result; 170 } 171 if (o instanceof int[]) { 172 final int[] numbers = (int[]) o; 173 final double[] result = Allocator.doubleArray(numbers.length); 174 Arrays.setAll(result, i -> numbers[i]); 175 return result; 176 } 177 if (o instanceof float[]) { 178 final float[] numbers = (float[]) o; 179 final double[] result = Allocator.doubleArray(numbers.length); 180 Arrays.setAll(result, i -> numbers[i]); 181 return result; 182 } 183 if (o instanceof double[]) { 184 final double[] numbers = (double[]) o; 185 return Arrays.copyOf(numbers, numbers.length); 186 } 187 188 throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); 189 // return null; 190 } 191 192 public double getDoubleValue() throws ImagingException { 193 final Object o = getValue(); 194 if (o == null) { 195 throw new ImagingException("Missing value: " + getTagInfo().getDescription()); 196 } 197 198 return ((Number) o).doubleValue(); 199 } 200 201 /** 202 * Returns the field's type, derived from bytes 2-3. 203 * 204 * @return the field's type, as a {@code FieldType} object. 205 */ 206 public AbstractFieldType getFieldType() { 207 return abstractFieldType; 208 } 209 210 public String getFieldTypeName() { 211 return getFieldType().getName(); 212 } 213 214 public int[] getIntArrayValue() throws ImagingException { 215 final Object o = getValue(); 216 // if (o == null) 217 // return null; 218 219 if (o instanceof Number) { 220 return new int[] { ((Number) o).intValue() }; 221 } 222 if (o instanceof Number[]) { 223 final Number[] numbers = (Number[]) o; 224 final int[] result = Allocator.intArray(numbers.length); 225 Arrays.setAll(result, i -> numbers[i].intValue()); 226 return result; 227 } 228 if (o instanceof short[]) { 229 final short[] numbers = (short[]) o; 230 final int[] result = Allocator.intArray(numbers.length); 231 Arrays.setAll(result, i -> 0xffff & numbers[i]); 232 return result; 233 } 234 if (o instanceof int[]) { 235 final int[] numbers = (int[]) o; 236 return Arrays.copyOf(numbers, numbers.length); 237 } 238 if (o instanceof long[]) { 239 final long[] numbers = (long[]) o; 240 final int[] iNumbers = new int[numbers.length]; 241 for (int i = 0; i < iNumbers.length; i++) { 242 iNumbers[i] = (int) numbers[i]; 243 } 244 return iNumbers; 245 } 246 247 throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); 248 // return null; 249 } 250 251 public int getIntValue() throws ImagingException { 252 final Object o = getValue(); 253 if (o == null) { 254 throw new ImagingException("Missing value: " + getTagInfo().getDescription()); 255 } 256 257 return ((Number) o).intValue(); 258 } 259 260 public int getIntValueOrArraySum() throws ImagingException { 261 final Object o = getValue(); 262 // if (o == null) 263 // return -1; 264 265 if (o instanceof Number) { 266 return ((Number) o).intValue(); 267 } 268 if (o instanceof Number[]) { 269 final Number[] numbers = (Number[]) o; 270 int sum = 0; 271 for (final Number number : numbers) { 272 sum += number.intValue(); 273 } 274 return sum; 275 } 276 if (o instanceof short[]) { 277 final short[] numbers = (short[]) o; 278 int sum = 0; 279 for (final short number : numbers) { 280 sum += number; 281 } 282 return sum; 283 } 284 if (o instanceof int[]) { 285 final int[] numbers = (int[]) o; 286 int sum = 0; 287 for (final int number : numbers) { 288 sum += number; 289 } 290 return sum; 291 } 292 293 throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); 294 // return -1; 295 } 296 297 /** 298 * Gets the value of the field in the form of an array of eight-byte (long) integers. 299 * 300 * @return an valid array of size zero or larger giving signed long integer values. 301 * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element. 302 */ 303 public long[] getLongArrayValue() throws ImagingException { 304 final Object o = getValue(); 305 if (o instanceof Number) { 306 return new long[] { ((Number) o).longValue() }; 307 } 308 if (o instanceof Number[]) { 309 final Number[] numbers = (Number[]) o; 310 final long[] result = Allocator.longArray(numbers.length); 311 Arrays.setAll(result, i -> numbers[i].longValue()); 312 return result; 313 } 314 if (o instanceof short[]) { 315 final short[] numbers = (short[]) o; 316 final long[] result = Allocator.longArray(numbers.length); 317 Arrays.setAll(result, i -> 0xffff & numbers[i]); 318 return result; 319 } 320 if (o instanceof int[]) { 321 final int[] numbers = (int[]) o; 322 final long[] result = Allocator.longArray(numbers.length); 323 Arrays.setAll(result, i -> 0xFFFFffffL & numbers[i]); 324 return result; 325 } 326 if (o instanceof long[]) { 327 final long[] numbers = (long[]) o; 328 return Arrays.copyOf(numbers, numbers.length); 329 } 330 331 throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); 332 } 333 334 /** 335 * Gets the value of the field in the form of an eight-byte (long) integer. 336 * 337 * @return a signed long integer value. 338 * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element. 339 */ 340 public long getLongValue() throws ImagingException { 341 final Object o = getValue(); 342 if (o == null) { 343 throw new ImagingException("Missing value: " + getTagInfo().getDescription()); 344 } 345 return ((Number) o).longValue(); 346 } 347 348 /** 349 * Returns the TIFF field's offset/value field, derived from bytes 8-11. 350 * 351 * @return the field's offset in a {@code long} of 4 packed bytes, or its inlined value <= 4 bytes long encoded in the field's byte order. 352 */ 353 public int getOffset() { 354 return (int) offset; 355 } 356 357 public AbstractTiffElement getOversizeValueElement() { 358 if (isLocalValue()) { 359 return null; 360 } 361 362 return new OversizeValueElement(getOffset(), value.length); 363 } 364 365 public int getSortHint() { 366 return sortHint; 367 } 368 369 public String getStringValue() throws ImagingException { 370 final Object o = getValue(); 371 if (o == null) { 372 return null; 373 } 374 if (!(o instanceof String)) { 375 throw new ImagingException("Expected String value(" + getTagInfo().getDescription() + "): " + o); 376 } 377 return (String) o; 378 } 379 380 /** 381 * Returns the field's tag, derived from bytes 0-1. 382 * 383 * @return the tag, as an {@code int} in which only the lowest 2 bytes are set 384 */ 385 public int getTag() { 386 return tag; 387 } 388 389 public TagInfo getTagInfo() { 390 return tagInfo; 391 } 392 393 public String getTagName() { 394 if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) { 395 return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")"; 396 } 397 return getTagInfo().name; 398 } 399 400 public Object getValue() throws ImagingException { 401 // System.out.print("getValue"); 402 return getTagInfo().getValue(this); 403 } 404 405 public String getValueDescription() { 406 try { 407 return getValueDescription(getValue()); 408 } catch (final ImagingException e) { 409 return "Invalid value: " + e.getMessage(); 410 } 411 } 412 413 private String getValueDescription(final Object o) { 414 if (o == null) { 415 return null; 416 } 417 418 if (o instanceof Number) { 419 return o.toString(); 420 } 421 if (o instanceof String) { 422 return "'" + o.toString().trim() + "'"; 423 } 424 if (o instanceof Date) { 425 final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ROOT); 426 return df.format((Date) o); 427 } 428 if (o instanceof Object[]) { 429 final Object[] objects = (Object[]) o; 430 final StringBuilder result = new StringBuilder(); 431 432 for (int i = 0; i < objects.length; i++) { 433 final Object object = objects[i]; 434 435 if (i > 50) { 436 result.append("... (").append(objects.length).append(")"); 437 break; 438 } 439 if (i > 0) { 440 result.append(", "); 441 } 442 result.append(object.toString()); 443 } 444 return result.toString(); 445 // } else if (o instanceof Number[]) 446 // { 447 // Number[] numbers = (Number[]) o; 448 // StringBuilder result = new StringBuilder(); 449 // 450 // for (int i = 0; i < numbers.length; i++) 451 // { 452 // Number number = numbers[i]; 453 // 454 // if (i > 0) 455 // result.append(", "); 456 // result.append("" + number); 457 // } 458 // return result.toString(); 459 // } 460 } 461 if (o instanceof short[]) { 462 final short[] values = (short[]) o; 463 final StringBuilder result = new StringBuilder(); 464 465 for (int i = 0; i < values.length; i++) { 466 final short sVal = values[i]; 467 468 if (i > 50) { 469 result.append("... (").append(values.length).append(")"); 470 break; 471 } 472 if (i > 0) { 473 result.append(", "); 474 } 475 result.append(sVal); 476 } 477 return result.toString(); 478 } 479 if (o instanceof int[]) { 480 final int[] values = (int[]) o; 481 final StringBuilder result = new StringBuilder(); 482 483 for (int i = 0; i < values.length; i++) { 484 final int iVal = values[i]; 485 486 if (i > 50) { 487 result.append("... (").append(values.length).append(")"); 488 break; 489 } 490 if (i > 0) { 491 result.append(", "); 492 } 493 result.append(iVal); 494 } 495 return result.toString(); 496 } 497 if (o instanceof long[]) { 498 final long[] values = (long[]) o; 499 final StringBuilder result = new StringBuilder(); 500 501 for (int i = 0; i < values.length; i++) { 502 final long lVal = values[i]; 503 504 if (i > 50) { 505 result.append("... (").append(values.length).append(")"); 506 break; 507 } 508 if (i > 0) { 509 result.append(", "); 510 } 511 result.append(lVal); 512 } 513 return result.toString(); 514 } 515 if (o instanceof double[]) { 516 final double[] values = (double[]) o; 517 final StringBuilder result = new StringBuilder(); 518 519 for (int i = 0; i < values.length; i++) { 520 final double dVal = values[i]; 521 522 if (i > 50) { 523 result.append("... (").append(values.length).append(")"); 524 break; 525 } 526 if (i > 0) { 527 result.append(", "); 528 } 529 result.append(dVal); 530 } 531 return result.toString(); 532 } 533 if (o instanceof byte[]) { 534 final byte[] values = (byte[]) o; 535 final StringBuilder result = new StringBuilder(); 536 537 for (int i = 0; i < values.length; i++) { 538 final byte bVal = values[i]; 539 540 if (i > 50) { 541 result.append("... (").append(values.length).append(")"); 542 break; 543 } 544 if (i > 0) { 545 result.append(", "); 546 } 547 result.append(bVal); 548 } 549 return result.toString(); 550 } 551 if (o instanceof char[]) { 552 final char[] values = (char[]) o; 553 final StringBuilder result = new StringBuilder(); 554 555 for (int i = 0; i < values.length; i++) { 556 final char cVal = values[i]; 557 558 if (i > 50) { 559 result.append("... (").append(values.length).append(")"); 560 break; 561 } 562 if (i > 0) { 563 result.append(", "); 564 } 565 result.append(cVal); 566 } 567 return result.toString(); 568 } 569 if (o instanceof float[]) { 570 final float[] values = (float[]) o; 571 final StringBuilder result = new StringBuilder(); 572 573 for (int i = 0; i < values.length; i++) { 574 final float fVal = values[i]; 575 576 if (i > 50) { 577 result.append("... (").append(values.length).append(")"); 578 break; 579 } 580 if (i > 0) { 581 result.append(", "); 582 } 583 result.append(fVal); 584 } 585 return result.toString(); 586 } 587 // else if (o instanceof short[]) 588 // { 589 // short[] numbers = (short[]) o; 590 // StringBuilder result = new StringBuilder(); 591 // 592 // for (int i = 0; i < numbers.length; i++) 593 // { 594 // short number = numbers[i]; 595 // 596 // if (i > 0) 597 // result.append(", "); 598 // result.append("" + number); 599 // } 600 // return result.toString(); 601 // } 602 603 return "Unknown: " + o.getClass().getName(); 604 } 605 606 /** 607 * Indicates whether the field's value is inlined into the offset field. 608 * 609 * @return true if the value is inlined 610 */ 611 public boolean isLocalValue() { 612 return count * abstractFieldType.getSize() <= TiffConstants.ENTRY_MAX_VALUE_LENGTH; 613 } 614 615 @Override 616 public String toString() { 617 return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): " + getValueDescription() + " (" + getCount() + " " 618 + getFieldType().getName() + ")"; 619 } 620}