001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2013 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.imageio.codec.jpeg; 040 041import org.dcm4che3.data.Attributes; 042import org.dcm4che3.data.Tag; 043import org.dcm4che3.data.UID; 044import org.dcm4che3.data.VR; 045import org.dcm4che3.util.ByteUtils; 046 047/** 048 * @author Gunter Zeilinger <gunterze@gmail.com> 049 * 050 */ 051public class JPEGHeader { 052 053 private final byte[] data; 054 private final int[] offsets; 055 056 public JPEGHeader(byte[] data, int lastMarker) { 057 int n = 0; 058 for (int offset = 0; (offset = nextMarker(data, offset)) != -1;) { 059 n++; 060 int marker = data[offset++] & 255; 061 if (JPEG.isStandalone(marker)) 062 continue; 063 if (offset+1 >= data.length) 064 break; 065 if (marker == lastMarker) 066 break; 067 offset += ByteUtils.bytesToUShortBE(data, offset); 068 } 069 this.data = data; 070 this.offsets = new int[n]; 071 for (int i = 0, offset = 0; i < n; i++) { 072 offsets[i] = (offset = nextMarker(data, offset)) ; 073 if (!JPEG.isStandalone(offset++ & 255)) 074 offset += ByteUtils.bytesToUShortBE(data, offset); 075 } 076 } 077 078 private static int nextMarker(byte[] data, int from) { 079 for (int i = from+1; i < data.length; i++) { 080 if (data[i-1] == -1 && data[i] != -1 && data[i] != 0) { 081 return i; 082 } 083 } 084 return -1; 085 } 086 087 public int offsetOf(int marker) { 088 for (int i = 0; i < offsets.length; i++) { 089 if (marker(i) == marker) 090 return offsets[i]; 091 } 092 return -1; 093 } 094 095 public int offsetSOF() { 096 for (int i = 0; i < offsets.length; i++) { 097 if (JPEG.isSOF(marker(i))) 098 return offsets[i]; 099 } 100 return -1; 101 } 102 103 public int offsetAfterAPP() { 104 for (int i = 1; i < offsets.length; i++) { 105 if (!JPEG.isAPP(marker(i))) 106 return offsets[i]; 107 } 108 return -1; 109 } 110 111 public int offset(int index) { 112 return offsets[index]; 113 } 114 115 public int marker(int index) { 116 return data[offsets[index]] & 255; 117 } 118 119 public int numberOfMarkers() { 120 return offsets.length; 121 } 122 123 /** 124 * Return corresponding Image Pixel Description Macro Attributes 125 * @param attrs target {@code Attributes} or {@code null} 126 * @return Image Pixel Description Macro Attributes 127 */ 128 public Attributes toAttributes(Attributes attrs) { 129 int offsetSOF = offsetSOF(); 130 if (offsetSOF == -1) 131 return null; 132 133 if (attrs == null) 134 attrs = new Attributes(10); 135 136 int sof = data[offsetSOF] & 255; 137 int p = data[offsetSOF+3] & 0xff; 138 int y = ((data[offsetSOF+3 + 1] & 0xff) << 8) 139 | (data[offsetSOF+3 + 2] & 0xff); 140 int x = ((data[offsetSOF+3 + 3] & 0xff) << 8) 141 | (data[offsetSOF+3 + 4] & 0xff); 142 int nf = data[offsetSOF+3 + 5] & 0xff; 143 attrs.setInt(Tag.SamplesPerPixel, VR.US, nf); 144 if (nf == 3) { 145 attrs.setString(Tag.PhotometricInterpretation, VR.CS, sof == JPEG.SOF0 ? "YBR_FULL_422" : "RGB"); 146 attrs.setInt(Tag.PlanarConfiguration, VR.US, 0); 147 } else { 148 attrs.setString(Tag.PhotometricInterpretation, VR.CS, "MONOCHROME2"); 149 } 150 attrs.setInt(Tag.Rows, VR.US, y); 151 attrs.setInt(Tag.Columns, VR.US, x); 152 attrs.setInt(Tag.BitsAllocated, VR.US, p > 8 ? 16 : 8); 153 attrs.setInt(Tag.BitsStored, VR.US, p); 154 attrs.setInt(Tag.HighBit, VR.US, p - 1); 155 attrs.setInt(Tag.PixelRepresentation, VR.US, 0); 156 if (!(sof == JPEG.SOF3 || (sof == JPEG.SOF55 && ss() == 0))) 157 attrs.setString(Tag.LossyImageCompression, VR.CS, "01"); 158 return attrs; 159 } 160 161 public String getTransferSyntaxUID() { 162 int sofOffset = offsetSOF(); 163 if (sofOffset == -1) 164 return null; 165 166 switch(data[sofOffset] & 255) { 167 case JPEG.SOF0: 168 return UID.JPEGBaseline1; 169 case JPEG.SOF1: 170 return UID.JPEGExtended24; 171 case JPEG.SOF3: 172 return ss() == 1 ? UID.JPEGLossless : UID.JPEGLosslessNonHierarchical14; 173 case JPEG.SOF55: 174 return ss() == 0 ? UID.JPEGLSLossless : UID.JPEGLSLossyNearLossless; 175 } 176 return null; 177 } 178 179 private int ss() { 180 int offsetSOS = offsetOf(JPEG.SOS); 181 return offsetSOS != -1 ? data[offsetSOS+6] & 255 : -1; 182 } 183}