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;
021
022import org.apache.commons.imaging.ImagingException;
023
024/**
025 * VP8 (bitstream) chunk.
026 *
027 * <pre>{@code
028 *  0                   1                   2                   3
029 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
030 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
031 * |                      ChunkHeader('VP8 ')                      |
032 * |                                                               |
033 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
034 * :                           VP8 data                            :
035 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
036 * }</pre>
037 *
038 * @see <a href="https://developers.google.com/speed/webp/docs/riff_container#simple_file_format_lossy">Simple File Format (Lossy)</a>
039 * @see <a href="https://datatracker.ietf.org/doc/html/rfc6386">VP8 Data Format and Decoding Guide</a>
040 * @since 1.0.0-alpha4
041 */
042public final class WebPChunkVp8 extends AbstractWebPChunk {
043    private final int versionNumber;
044    private final int width;
045    private final int height;
046    private final int horizontalScale;
047    private final int verticalScale;
048
049    /**
050     * Create a VP8 chunk.
051     *
052     * @param type  chunk type.
053     * @param size  chunk size.
054     * @param bytes chunk data.
055     * @throws ImagingException if the chunk data and the size provided do not match.
056     */
057    public WebPChunkVp8(final int type, final int size, final byte[] bytes) throws ImagingException {
058        super(type, size, bytes);
059
060        if (size < 10) {
061            throw new ImagingException("Invalid VP8 chunk");
062        }
063
064        /*
065         * https://datatracker.ietf.org/doc/html/rfc6386#section-9
066         */
067
068        /*
069         * Frame Header:
070         *
071         * 1. A 1-bit frame type (0 for key frames, 1 for interframes).
072         *
073         * 2. A 3-bit version number (0 - 3 are defined as four different profiles with different decoding complexity; other values may be defined for future
074         * variants of the VP8 data format).
075         *
076         * 3. A 1-bit show_frame flag (0 when current frame is not for display, 1 when current frame is for display).
077         *
078         * 4. A 19-bit field containing the size of the first data partition in bytes.
079         */
080
081        final int b0 = bytes[0] & 0xFF;
082        // int b1 = bytes[1] & 0xFF;
083        // int b2 = bytes[2] & 0xFF;
084
085        if ((b0 & 0b1) != 0) {
086            throw new ImagingException("Invalid VP8 chunk: should be key frame");
087        }
088
089        this.versionNumber = (b0 & 0b1110) >> 1;
090        if ((b0 & 0b0001_0000) == 0) {
091            throw new ImagingException("Invalid VP8 chunk: frame should to be display");
092        }
093
094        // int firstDataPartitionSize = (b0 >>> 5) + (b1 << 3) + (b2 << 11);
095
096        /*
097         * Key Frame:
098         *
099         * Start code byte 0 0x9d Start code byte 1 0x01 Start code byte 2 0x2a
100         *
101         * 16 bits : (2 bits Horizontal Scale << 14) | Width (14 bits) 16 bits : (2 bits Vertical Scale << 14) | Height (14 bits)
102         */
103
104        final int b3 = bytes[3] & 0xFF;
105        final int b4 = bytes[4] & 0xFF;
106        final int b5 = bytes[5] & 0xFF;
107        final int b6 = bytes[6] & 0xFF;
108        final int b7 = bytes[7] & 0xFF;
109        final int b8 = bytes[8] & 0xFF;
110        final int b9 = bytes[9] & 0xFF;
111
112        if (b3 != 0x9D || b4 != 0x01 || b5 != 0x2A) {
113            throw new ImagingException("Invalid VP8 chunk: invalid signature");
114        }
115
116        this.width = b6 + ((b7 & 0b0011_1111) << 8);
117        this.horizontalScale = b7 >> 6;
118        this.height = b8 + ((b9 & 0b0011_1111) << 8);
119        this.verticalScale = b9 >> 6;
120    }
121
122    @Override
123    public void dump(final PrintWriter pw, final int offset) throws ImagingException, IOException {
124        super.dump(pw, offset);
125        pw.println("  Version Number: " + getVersionNumber());
126        pw.println("  Width: " + getWidth());
127        pw.println("  Height: " + getHeight());
128        pw.println("  Horizontal Scale: " + getHorizontalScale());
129        pw.println("  Vertical Scale: " + getVerticalScale());
130    }
131
132    /**
133     * @return the height.
134     */
135    public int getHeight() {
136        return height;
137    }
138
139    /**
140     * @return the horizontal scale.
141     */
142    public int getHorizontalScale() {
143        return horizontalScale;
144    }
145
146    /**
147     * @return the version number.
148     */
149    public int getVersionNumber() {
150        return versionNumber;
151    }
152
153    /**
154     * @return the vertical scale.
155     */
156    public int getVerticalScale() {
157        return verticalScale;
158    }
159
160    /**
161     * @return the width.
162     */
163    public int getWidth() {
164        return width;
165    }
166}