Skip to content

Commit

Permalink
WebP read support (#63)
Browse files Browse the repository at this point in the history
Support for reading WebP as requested in #48.
  • Loading branch information
StefanOltmann authored Jan 21, 2024
1 parent dc361f3 commit 16c03b8
Show file tree
Hide file tree
Showing 41 changed files with 780 additions and 114 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ It's part of [Ashampoo Photos](https://ashampoo.com/photos).
* JPG: Read & Write EXIF, IPTC & XMP
* PNG: Read & Write `eXIf` chunk & XMP
+ Also read non-standard EXIF & IPTC from `tEXt`/`zTXt` chunk
* WebP: Read EXIF & XMP
* HEIC / AVIF: Read EXIF & XMP
* JPEG XL: Read EXIF & XMP of uncompressed files
* TIFF / DNG / RAW: Read EXIF & XMP
Expand All @@ -35,7 +36,7 @@ of Ashampoo Photos, which, in turn, is driven by user community feedback.
## Installation

```
implementation("com.ashampoo:kim:0.10")
implementation("com.ashampoo:kim:0.11")
```

## Sample usages
Expand Down
7 changes: 5 additions & 2 deletions src/commonMain/kotlin/com/ashampoo/kim/format/ImageParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.ashampoo.kim.format.jpeg.JpegImageParser
import com.ashampoo.kim.format.png.PngImageParser
import com.ashampoo.kim.format.raf.RafImageParser
import com.ashampoo.kim.format.tiff.TiffImageParser
import com.ashampoo.kim.format.webp.WebPImageParser
import com.ashampoo.kim.input.ByteReader
import com.ashampoo.kim.model.ImageFormat

Expand All @@ -38,6 +39,8 @@ fun interface ImageParser {

ImageFormat.PNG -> PngImageParser

ImageFormat.WEBP -> WebPImageParser

ImageFormat.TIFF,
ImageFormat.CR2,
ImageFormat.NEF,
Expand All @@ -47,8 +50,8 @@ fun interface ImageParser {

ImageFormat.RAF -> RafImageParser

ImageFormat.HEIC -> BaseMediaFileFormatImageParser
ImageFormat.AVIF -> BaseMediaFileFormatImageParser
ImageFormat.HEIC,
ImageFormat.AVIF,
ImageFormat.JXL -> BaseMediaFileFormatImageParser

else -> null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object BaseMediaFileFormatImageParser : ImageParser {
if (allBoxes.isEmpty())
throw ImageReadException("Illegal ISOBMFF: Has no boxes.")

val fileTypeBox = allBoxes.find { it.type == BoxType.FTYP } as? FileTypeBox
val fileTypeBox = allBoxes.filterIsInstance<FileTypeBox>().firstOrNull()

if (fileTypeBox == null)
throw ImageReadException("Illegal ISOBMFF: Has no 'ftyp' Box.")
Expand All @@ -68,7 +68,7 @@ object BaseMediaFileFormatImageParser : ImageParser {
if (fileTypeBox.majorBrand == FileTypeBox.JXL_BRAND)
return JxlHandler.createMetadata(allBoxes)

val metaBox = allBoxes.find { it.type == BoxType.META } as? MetaBox
val metaBox = allBoxes.filterIsInstance<MetaBox>().firstOrNull()

if (metaBox == null)
throw ImageReadException("Illegal ISOBMFF: Has no 'meta' Box.")
Expand Down Expand Up @@ -128,7 +128,7 @@ object BaseMediaFileFormatImageParser : ImageParser {
imageSize = null, // not covered by ISO BMFF
exif = exif,
exifBytes = exifBytes,
iptc = null, // not covered by ISO BMFF
iptc = null, // not supported by ISO BMFF
xmp = xmp
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import com.ashampoo.kim.format.bmff.box.XmlBox
import com.ashampoo.kim.input.PositionTrackingByteReader

/**
* Reads ISOBMFF Boxes
* Reads ISOBMFF boxes
*/
object BoxReader {

Expand Down Expand Up @@ -110,7 +110,7 @@ object BoxReader {
BoxType.EXIF -> ExifBox(globalOffset, actualLength, bytes)
BoxType.XML -> XmlBox(globalOffset, actualLength, bytes)
BoxType.JXLP -> JxlParticalCodestreamBox(globalOffset, actualLength, bytes)
else -> Box(globalOffset, type, actualLength, bytes)
else -> Box(type, globalOffset, actualLength, bytes)
}

boxes.add(box)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ internal object JxlHandler {

fun createMetadata(allBoxes: List<Box>): ImageMetadata {

val exifBox = allBoxes.find { it.type == BoxType.EXIF } as? ExifBox

val xmlBox = allBoxes.find { it.type == BoxType.XML } as? XmlBox
val exifBox = allBoxes.filterIsInstance<ExifBox>().firstOrNull()
val xmlBox = allBoxes.filterIsInstance<XmlBox>().firstOrNull()

return ImageMetadata(
imageFormat = ImageFormat.JXL, // could be any ISO BMFF
Expand Down
4 changes: 2 additions & 2 deletions src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/Box.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ package com.ashampoo.kim.format.bmff.box
import com.ashampoo.kim.format.bmff.BoxType

open class Box(
val offset: Long,
val type: BoxType,
val offset: Long,
val length: Long,
/* Payload bytes, not including type & length bytes */
val payload: ByteArray
) {

override fun toString(): String =
"Box $type @ $offset ($length bytes)"
"Box '$type' @ $offset ($length bytes)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ExifBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.EXIF, length, payload) {
) : Box(BoxType.EXIF, offset, length, payload) {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class FileTypeBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.FTYP, length, payload) {
) : Box(BoxType.FTYP, offset, length, payload) {

val majorBrand: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HandlerReferenceBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.HDLR, length, payload) {
) : Box(BoxType.HDLR, offset, length, payload) {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ItemInfoEntryBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.INFE, length, payload) {
) : Box(BoxType.INFE, offset, length, payload) {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ItemInformationBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.IINF, length, payload), BoxContainer {
) : Box(BoxType.IINF, offset, length, payload), BoxContainer {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ItemLocationBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.ILOC, length, payload) {
) : Box(BoxType.ILOC, offset, length, payload) {

/**
* The version of the box.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class JxlParticalCodestreamBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.JXLP, length, payload) {
) : Box(BoxType.JXLP, offset, length, payload) {

val isHeader: Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MediaDataBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.MDAT, length, payload) {
) : Box(BoxType.MDAT, offset, length, payload) {

override fun toString(): String =
"$type Box"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MetaBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.META, length, payload), BoxContainer {
) : Box(BoxType.META, offset, length, payload), BoxContainer {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class PrimaryItemBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.PITM, length, payload) {
) : Box(BoxType.PITM, offset, length, payload) {

val version: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class XmlBox(
offset: Long,
length: Long,
payload: ByteArray
) : Box(offset, BoxType.XML, length, payload) {
) : Box(BoxType.XML, offset, length, payload) {

val xmp: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.ashampoo.kim.format.png

import com.ashampoo.kim.common.toFourCCTypeString
import com.ashampoo.kim.format.png.PngConstants.TPYE_LENGTH

/**
* Type of a PNG chunk.
Expand Down Expand Up @@ -75,8 +76,8 @@ data class PngChunkType internal constructor(
@Suppress("MagicNumber")
fun of(typeBytes: ByteArray): PngChunkType {

require(typeBytes.size == PngConstants.TPYE_LENGTH) {
"ChunkType must be always 4 bytes!"
require(typeBytes.size == TPYE_LENGTH) {
"ChunkType must be always 4 bytes, but got ${typeBytes.size} bytes!"
}

@Suppress("UnnecessaryParentheses")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ object PngConstants {

val PNG_BYTE_ORDER = ByteOrder.BIG_ENDIAN

/* ChunkType must be always 4 bytes */
/* ChunkType is a FourCC, so it's 4 bytes. */
const val TPYE_LENGTH = 4

const val COMPRESSION_DEFLATE_INFLATE = 0

@Suppress("MagicNumber")
val PNG_SIGNATURE = byteArrayOf(
0x89.toByte(),
'P'.code.toByte(),
Expand Down
Loading

0 comments on commit 16c03b8

Please sign in to comment.