Skip to content

Commit

Permalink
Improved tests, UserComment support, Tags no case classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ngrossmann committed Feb 12, 2013
1 parent 25a53cd commit 3fec5c3
Show file tree
Hide file tree
Showing 18 changed files with 117 additions and 151 deletions.
7 changes: 6 additions & 1 deletion src/main/scala/net/n12n/exif/ByteSeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ class ByteSeq(a: Array[Byte]) {

override def hashCode() = array.product

def slice(start:Int, end:Int) = new ByteSeq(array.slice(start, end))
def slice(start:Int, end: Int = length) = new ByteSeq(array.slice(start, end))

def toArray(): Array[Byte] = {
val a = new Array[Byte](length)
Array.copy(array, 0, a, 0, length)
a
}
def toShort(offset: Int, byteOrder: ByteOrder): Int = toNumber(offset, byteOrder, Type.Short.size).toInt

def toSignedShort(offset: Int, byteOrder: ByteOrder) =
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/net/n12n/exif/Exif.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object Exif extends App {
}

private def usage() {
System.err.println("Usage: Exif <path>")
System.err.println("Usage: Exif <path>")
System.exit(1)
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/net/n12n/exif/ExifIfd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ class ExifIfd(exif: ExifSegment, offset: Int) extends Ifd(exif, offset, "Exif IF
override type T = ExifTag
override val Tags = ExifIfd.Tags
override protected def createTag(marker: Int) =
ExifTag(marker, "Unknown Exif Tag %04x".format(marker))
new ExifTag(marker, "Unknown Exif Tag %04x".format(marker))
}

case class ExifTag(marker: Int, name: String) extends Tag
class ExifTag(marker: Int, name: String) extends Tag(marker, name)

object ExifIfd {

Expand All @@ -44,7 +44,7 @@ object ExifIfd {
val FlashpixVersion = new ExifTag(40960, "FlashpixVersion") with UndefinedTag
val ColorSpace = new ExifTag(40961, "ColorSpace") with ShortTag
val MakerNote = new ExifTag(37500, "MakerNote") with UndefinedTag
val UserComment = new ExifTag(37510, "UserComment") with UndefinedTag
val UserComment = new ExifTag(37510, "UserComment") with UserCommentTag
val RelatedSoundFile = new ExifTag(40964, "RelatedSoundFile") with AsciiTag
val DateTimeOriginal = new ExifTag(36867, "DateTimeOriginal") with AsciiTag
val DateTimeDigitized = new ExifTag(36868, "DateTimeDigitized") with AsciiTag
Expand Down
13 changes: 5 additions & 8 deletions src/main/scala/net/n12n/exif/ExifSegment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,12 @@ class ExifSegment(length: Int, data: ByteSeq, offset: Int = 0)
/** Optional 1st IFD. */
val ifd1: Option[TiffIfd] = if (ifd0.nextIfd != 0) Some(new TiffIfd(this, ifd0.nextIfd, "IFD1")) else None
/** Optional Exif IFD. */
val exifIfd: Option[ExifIfd] = ifd0.tags.find(_.tag == TiffIfd.ExifIfdPointer) match {
case Some(pointer: LongIFD) => Some(new ExifIfd(this, pointer.value.head.toInt))
case None => None
}
val exifIfd: Option[ExifIfd] = ifd0.findValue(TiffIfd.ExifIfdPointer).
map((pointer) => new ExifIfd(this, pointer.toInt))
/** Optional GPS IFD. */
val gpsIfd: Option[GpsIfd] = ifd0.tags.find(_.tag == TiffIfd.GpsInfoIfdPointer) match {
case Some(pointer: LongIFD) => Some(new GpsIfd(this, pointer.value.head.toInt))
case None => None
}
val gpsIfd: Option[GpsIfd] = ifd0.findValue(TiffIfd.GpsInfoIfdPointer).
map((pointer => new GpsIfd(this, pointer.toInt)))

/** List of all IFDs in this Exif segment. */
lazy val ifds = ifd0 :: ifd1.toList ::: exifIfd.toList ::: gpsIfd.toList

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/net/n12n/exif/GpsIfd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ package net.n12n.exif
class GpsIfd(exif: ExifSegment, offset: Int) extends Ifd(exif, offset, "GPS IFD") {
override type T = GpsTag
override val Tags = GpsIfd.Tags
override protected def createTag(marker: Int) = GpsTag(marker, "Unknown GPS Tag")
override protected def createTag(marker: Int) = new GpsTag(marker, "Unknown GPS Tag")
}

case class GpsTag(val marker: Int, val name: String) extends Tag
class GpsTag(marker: Int, name: String) extends Tag(marker, name)

object GpsIfd {

Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/net/n12n/exif/Ifd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,16 @@ abstract class Ifd(exif: ExifSegment, offset: Int, val name: String) {
*/
def attr(tag: T): IfdAttribute = findAttr(tag).getOrElse(throw AttributeNotFoundException(tag.name))

def findValue[V](tag: T with TypedTag[V]): Option[V] = tags.find(_.tag == tag).
map(tag.value(_, exif.byteOrder))

/**
* Get attribute value by tag.
* @param tag Tag
* @return Attribute value
* @throws AttributeNotFoundException If the attribute was not found in this IFD.
*/
def value[V](tag: TypedTag[V]): V = {
def value[V](tag: T with TypedTag[V]): V = {
val ifdAttr: IfdAttribute = tags.find(_.tag == tag).getOrElse(
throw AttributeNotFoundException(tag.name))
tag.value(ifdAttr, exif.byteOrder)
Expand Down
89 changes: 18 additions & 71 deletions src/main/scala/net/n12n/exif/IfdAttribute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,15 @@ object IfdAttribute {
}


def create[T <: Tag](namedTag: T, tagType: Type, count: Int, data: ByteSeq, order: ByteOrder):
private def create[T <: Tag](namedTag: T, tagType: Type, count: Int, data: ByteSeq, order: ByteOrder):
IfdAttribute = {
tagType match {
case Type.Ascii => new AsciiIFD(namedTag, count, data)
case Type.Byte => new ByteIFD(namedTag, count, data)
case Type.Long => new LongIFD(namedTag, count, data, order)
case Type.Rational => new RationalIFD(namedTag, count, data, order)
case Type.Short => new ShortIFD(namedTag, count, data, order)
case Type.SLong => new SignedLongIFD(namedTag, count, data, order)
case Type.SRational => new SignedRationalIFD(namedTag, count, data, order)
case Type.Undefined => new UndefinedIFD(namedTag, count, data)
case _ => new UnknownIFD(namedTag, tagType, count, data)
case Type.Ascii => new GenericIfdAttribute(namedTag, Type.Ascii, count, data, order)
case Type.Byte => new GenericIfdAttribute(namedTag, Type.Byte, count, data, order)
case Type.Undefined => new GenericIfdAttribute(namedTag, Type.Byte, count, data, order)
case gt: GenericType[_] if (count == 1) =>
new GenericIfdAttribute(namedTag, gt, count, data, order)
case gt: GenericType[_] => new GenericIfdListAttribute(namedTag, gt, count, data, order)
}
}
}
Expand All @@ -76,68 +73,18 @@ abstract class IfdAttribute(val tag: Tag, val tagType: Type, val count: Int,
}
}

class AsciiIFD(tag: Tag, count: Int, data: ByteSeq)
extends IfdAttribute(tag, Type.Ascii, count, data) {
type V = String
val value = data.zstring(0)
class GenericIfdAttribute[T](tag: Tag, tagType: GenericType[T], count: Int, data: ByteSeq,
order: ByteOrder)
extends IfdAttribute(tag, tagType, count, data) {
override type V = T
override val value: V = tagType.toScala(data, 0, order)
}

class ByteIFD(tag: Tag, count: Int, data: ByteSeq)
extends IfdAttribute(tag, Type.Byte, count, data) {
type V = ByteSeq
override val value = data
class GenericIfdListAttribute[T](tag: Tag, tagType: GenericType[T], count: Int, data: ByteSeq,
order: ByteOrder)
extends IfdAttribute(tag, tagType, count, data) {
override type V = List[T]
override val value: V = (for (i <- 0 until count)
yield tagType.toScala(data,i * tagType.size, order)).toList
}

class ShortIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.Short, count, data) {
type V = Seq[Int]
val value: Seq[Int] = for (i <- 0 until count) yield data.toShort(i * tagType.size, order)
}

class LongIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.Long, count, data) {
type V = Seq[Long]
val value: Seq[Long] = for (i <- 0 until count) yield data.toLong(i * tagType.size, order)
}

class LongIFD2(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.Long, count, data) {
type V = Seq[Long]
val value: Seq[Long] = for (i <- 0 until count) yield data.toLong(i * tagType.size, order)
}

class RationalIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.Rational, count, data) {
type V = Seq[Rational]
val value: Seq[Rational] = for (i <- 0 until count) yield data.toRational(i * tagType.size, order)
}

class UndefinedIFD(tag: Tag, count: Int, data: ByteSeq)
extends IfdAttribute(tag, Type.Ascii, count, data) {
type V = ByteSeq
val value = data
}

class SignedShortIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.Short, count, data) {
type V = Seq[Short]
val value: Seq[Short] = for (i <- 0 until count) yield data.toSignedShort(i * tagType.size, order)
}

class SignedLongIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.SLong, count, data) {
type V = Seq[Int]
val value: Seq[Int] = for (i <- 0 until count) yield data.toSignedLong(i * tagType.size, order)
}

class SignedRationalIFD(tag: Tag, count: Int, data: ByteSeq, order: ByteOrder)
extends IfdAttribute(tag, Type.SRational, count, data) {
type V = Seq[SignedRational]
val value: Seq[SignedRational] = for (i <- 0 until count) yield
data.toSignedRational(i * tagType.size, order)
}

class UnknownIFD(tag: Tag, tagType: Type, count: Int, data: ByteSeq) extends IfdAttribute(tag, tagType, count, data) {
type V = ByteSeq
override val value = data
}
10 changes: 5 additions & 5 deletions src/main/scala/net/n12n/exif/JpegMetaData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ class JpegMetaData(data: InputStream) {
" found " + soi)
val segments = parseSegments(in)
/** List of comment segments */
val comments = segments.filter(_.marker == Segment.ComMarker)
val comments: List[ComSegment] = segments.filter(_.marker == Segment.ComMarker).
map(_.asInstanceOf[ComSegment])

val exif: Option[ExifSegment] =
segments.find(_.isInstanceOf[ExifSegment]).map(_.asInstanceOf[ExifSegment])

val exif: Option[ExifSegment] = segments.find(_.isInstanceOf[ExifSegment]) match {
case Some(exif: ExifSegment) => Some(exif)
case _ => None
}
/**
* Total size of the image.
*/
Expand Down
26 changes: 13 additions & 13 deletions src/main/scala/net/n12n/exif/Segment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@
package net.n12n.exif

object Segment {
case class Marker(marker: Int, name: String) extends Tag
val JfifMarker = Marker(0xffe0, "JFIF")
val App1Marker = Marker(0xffe1, "APP1")
val App2Marker = Marker(0xffe2, "APP2")
val DqtMarker = Marker(0xffdb, "DQT")
val DhtMarker = Marker(0xffc4, "DHT")
val DriMarker = Marker(0xffdd, "DRI")
val SofMarker = Marker(0xffc0, "SOF")
val SosMarker = Marker(0xffda, "SOS")
val EoiMarker = Marker(0xffd9, "EOI")
val ComMarker = Marker(0xfffe, "Comment")
class Marker(marker: Int, name: String) extends Tag(marker, name)
val JfifMarker = new Marker(0xffe0, "JFIF")
val App1Marker = new Marker(0xffe1, "APP1")
val App2Marker = new Marker(0xffe2, "APP2")
val DqtMarker = new Marker(0xffdb, "DQT")
val DhtMarker = new Marker(0xffc4, "DHT")
val DriMarker = new Marker(0xffdd, "DRI")
val SofMarker = new Marker(0xffc0, "SOF")
val SosMarker = new Marker(0xffda, "SOS")
val EoiMarker = new Marker(0xffd9, "EOI")
val ComMarker = new Marker(0xfffe, "COM")
val Exif = ByteSeq("Exif")

val Markers = Set(JfifMarker, App1Marker, App2Marker, DqtMarker, DhtMarker, DriMarker,
Expand All @@ -40,14 +40,14 @@ object Segment {
val markerValue = marker.toShort(0, ByteOrder.BigEndian)
val tag = Markers.find(_.marker == markerValue) match {
case Some(t) => t
case None => Marker(markerValue, "Unknown Marker")
case None => new Marker(markerValue, "Unknown Marker")
}
val length = (in.next() << 8) + in.next()
val data = ByteSeq(length - 2, in)

if (tag == Segment.App1Marker && data.slice(0, Exif.length) == Exif)
new ExifSegment(length, data)
else if (marker == ComMarker)
else if (tag == ComMarker)
new ComSegment(ComMarker, length, data)
else {
new Segment(tag, length, data)
Expand Down
16 changes: 13 additions & 3 deletions src/main/scala/net/n12n/exif/Tag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@ package net.n12n.exif
* @param marker Marking 2-byte sequence, must be in range 0x0 <= marker < 0x10000.
* @param name Tag name.
*/
abstract class Tag {
val marker: Int
val name: String
abstract class Tag(val marker: Int, val name: String) {
require(marker < 0x10000 && marker >= 0)

/**
* Two tags are equal if they are of the exactly same type
* and have the same marker value.
* @return `true` If tags are equal.
*/
override def equals(that: Any): Boolean = {
if (that != null && that.isInstanceOf[Tag]) {
val other = that.asInstanceOf[Tag]
this.getClass() == that.getClass() && other.marker == marker
} else false
}

override def toString() = "%s(%04x)".format(name, marker)
}

5 changes: 3 additions & 2 deletions src/main/scala/net/n12n/exif/TiffIfd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ package net.n12n.exif
class TiffIfd(exif: ExifSegment, offset: Int, name: String) extends Ifd(exif, offset, name) {
override type T = TiffTag
override val Tags = TiffIfd.Tags
override protected def createTag(marker: Int) = TiffTag(marker, "Unknown Tiff Tag")
override protected def createTag(marker: Int) = new TiffTag(marker, "Unknown Tiff Tag")
}

case class TiffTag(marker: Int, name: String) extends Tag
class TiffTag(marker: Int, name: String) extends Tag(marker, name)

object TiffIfd {
val ImageWidth = new TiffTag(256, "ImageWidth") with NumericTag
Expand All @@ -48,6 +48,7 @@ object TiffIfd {
val GpsInfoIfdPointer = new TiffTag(0x8825, "GpsInfoIfdPointer") with LongTag
val XResolution = new TiffTag(282, "XResolution") with RationalTag
val YResolution = new TiffTag(283, "YResolution") with RationalTag
/** 2 = inches, 3 = centimeters, others = reserved. */
val ResolutionUnit = new TiffTag(296, "ResolutionUnit") with ShortTag
val StripOffsets = new TiffTag(273, "StripOffsets") with NumericListTag
val RowsPerStrip = new TiffTag(278, "RowsPerStrip") with NumericTag
Expand Down
31 changes: 13 additions & 18 deletions src/main/scala/net/n12n/exif/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,54 +27,49 @@ abstract class Type(val id: Int, val size: Int, val name: String) {
def toScala(data: ByteSeq, offset: Int, order: ByteOrder): ScalaType
}

abstract class GenericType[T](id: Int, size: Int, name: String) extends Type(id, size, name) {
override type ScalaType = T
}

/**
* Simple types used in JPEG/EXIF.
*/
object Type {
/** One byte value. */
val Byte = new Type(1, 1, "BYTE") {
type ScalaType = ByteSeq
val Byte = new GenericType[ByteSeq](1, 1, "BYTE") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.slice(offset, data.length)
}
/** One byte character. */
val Ascii = new Type(2, 1, "ASCII") {
type ScalaType = String
val Ascii = new GenericType[String](2, 1, "ASCII") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.zstring(offset)
}
/** Two byte unsigned integer. */
val Short = new Type(3, 2, "SHORT") {
type ScalaType = Int
val Short = new GenericType[Int](3, 2, "SHORT") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.toShort(offset, order)
}

/** A 4 byte unsigned integer. */
val Long = new Type(4, 4, "LONG") {
type ScalaType = Long
val Long = new GenericType[Long](4, 4, "LONG") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.toLong(offset, order)
}
/** A rational value made up of two `net.n12n.exif.Type.Long` values. */
val Rational = new Type(5, 8, "RATIONAL") {
type ScalaType = Rational
val Rational = new GenericType[Rational](5, 8, "RATIONAL") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.toRational(offset, order)
}
/** Undefined type. */
val Undefined = new Type(7, 1, "UNDEFINED") {
type ScalaType = ByteSeq
val Undefined = new GenericType[ByteSeq](7, 1, "UNDEFINED") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.slice(offset, data.length)
}
/** Signed 4 byte integer. */
val SLong = new Type(9, 4, "SLONG") {
type ScalaType = Int
val SLong = new GenericType[Int](9, 4, "SLONG") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.toSignedLong(offset, order)
}
/** Signed rational, two 4 byte integers. */
val SRational = new Type(10, 8, "SRATIONAL") {
type ScalaType = SignedRational
val SRational = new GenericType[SignedRational](10, 8, "SRATIONAL") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.toSignedRational(offset, order)
}
/** Fall back in case type is not know. */
val Unknown = new Type(11, 1, "UNKNOWN") {
type ScalaType = ByteSeq
val Unknown = new GenericType[ByteSeq](11, 1, "UNKNOWN") {
override def toScala(data: ByteSeq, offset: Int, order: ByteOrder) = data.slice(offset, data.length)
}

Expand Down
Loading

0 comments on commit 3fec5c3

Please sign in to comment.