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.webp.chunks;
018
019import java.io.IOException;
020import java.io.PrintWriter;
021import java.nio.ByteOrder;
022import java.nio.charset.StandardCharsets;
023
024import org.apache.commons.imaging.ImagingException;
025import org.apache.commons.imaging.common.BinaryFileParser;
026import org.apache.commons.imaging.internal.SafeOperations;
027
028/**
029 * A WebP image is composed of several chunks. This is the base class for the chunks, used by the parser.
030 *
031 * @see <a href="https://developers.google.com/speed/webp/docs/riff_container">WebP Container Specification</a>
032 * @since 1.0.0-alpha4
033 */
034public abstract class AbstractWebPChunk extends BinaryFileParser {
035
036    private static boolean checkArgs(final int size, final byte[] bytes) throws ImagingException {
037        if (size != bytes.length) {
038            throw new ImagingException("Chunk size must match bytes length");
039        }
040        return true;
041    }
042
043    private final int type;
044    private final int size;
045    protected final byte[] bytes;
046    private final int chunkSize;
047
048    /**
049     * Create a new WebP chunk.
050     *
051     * @param type  chunk type.
052     * @param size  chunk size.
053     * @param bytes chunk data.
054     * @throws ImagingException if the chunk data and the size provided do not match.
055     */
056    public AbstractWebPChunk(final int type, final int size, final byte[] bytes) throws ImagingException {
057        this(type, size, bytes, checkArgs(size, bytes));
058    }
059
060    private AbstractWebPChunk(final int type, final int size, final byte[] bytes, final boolean ignored) {
061        super(ByteOrder.LITTLE_ENDIAN);
062        this.type = type;
063        this.size = bytes.length;
064        this.bytes = bytes;
065        // if chunk size is odd, a single padding byte is added
066        final int padding = size % 2 != 0 ? 1 : 0;
067        // Chunk FourCC (4 bytes) + Chunk Size (4 bytes) + Chunk Payload (n bytes) + Padding
068        this.chunkSize = SafeOperations.add(4, 4, size, padding);
069    }
070
071    /**
072     * Print the chunk to the given stream.
073     *
074     * @param pw     a stream to write to.
075     * @param offset chunk offset.
076     * @throws ImagingException if the image is invalid.
077     * @throws IOException      if it fails to write to the given stream.
078     */
079    public void dump(final PrintWriter pw, final int offset) throws ImagingException, IOException {
080        pw.printf("Chunk %s at offset %s, length %d%n, payload size %d%n", getTypeDescription(), offset >= 0 ? String.valueOf(offset) : "unknown",
081                getChunkSize(), getPayloadSize());
082    }
083
084    /**
085     * @return a copy of the chunk data as bytes.
086     */
087    public byte[] getBytes() {
088        return bytes.clone();
089    }
090
091    /**
092     * @return the chunk size.
093     */
094    public int getChunkSize() {
095        return chunkSize;
096    }
097
098    /**
099     * @return the payload size.
100     */
101    public int getPayloadSize() {
102        return size;
103    }
104
105    /**
106     * @return the chunk type.
107     */
108    public int getType() {
109        return type;
110    }
111
112    /**
113     * @return the description of the chunk type.
114     */
115    public String getTypeDescription() {
116        return new String(new byte[] { (byte) (type & 0xff), (byte) (type >> 8 & 0xff), (byte) (type >> 16 & 0xff), (byte) (type >> 24 & 0xff) },
117                StandardCharsets.UTF_8);
118    }
119}