Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8221bbd authored by Ruben Brunk's avatar Ruben Brunk
Browse files

Added jpeg streaming classes.

- Provides streaming operations for decompressing/compressing
  JPEG files.
- Allows pixel operations to be performed on large JPEG images
  without holding the entire bitmap in memory.

Change-Id: I597ddf282b59d2ba6d6bca4722208121e3728f94
parent 0c1b4c64
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -37,9 +37,9 @@ LOCAL_SDK_VERSION := current
# the libraries in the APK, otherwise just put them in /system/lib and
# leave them out of the APK
ifneq (,$(TARGET_BUILD_APPS))
  LOCAL_JNI_SHARED_LIBRARIES := libjni_eglfence libjni_filtershow_filters librsjni
  LOCAL_JNI_SHARED_LIBRARIES := libjni_eglfence libjni_filtershow_filters librsjni libjni_jpegstream
else
  LOCAL_REQUIRED_MODULES := libjni_eglfence libjni_filtershow_filters
  LOCAL_REQUIRED_MODULES := libjni_eglfence libjni_filtershow_filters libjni_jpegstream
endif

LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+193 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.jpegstream;

import android.graphics.Point;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class JPEGInputStream extends FilterInputStream {
    private long JNIPointer = 0; // Used by JNI code. Don't touch.

    private boolean mValidConfig = false;
    private boolean mConfigChanged = false;
    private int mFormat = -1;
    private byte[] mTmpBuffer = new byte[1];
    private int mWidth = 0;
    private int mHeight = 0;

    public JPEGInputStream(InputStream in) {
        super(in);
    }

    public JPEGInputStream(InputStream in, int format) {
        super(in);
        setConfig(format);
    }

    public boolean setConfig(int format) {
        // Make sure format is valid
        switch (format) {
            case JpegConfig.FORMAT_GRAYSCALE:
            case JpegConfig.FORMAT_RGB:
            case JpegConfig.FORMAT_ABGR:
            case JpegConfig.FORMAT_RGBA:
                break;
            default:
                return false;
        }
        mFormat = format;
        mValidConfig = true;
        mConfigChanged = true;
        return true;
    }

    public Point getDimensions() throws IOException {
        if (mValidConfig) {
            applyConfigChange();
            return new Point(mWidth, mHeight);
        }
        return null;
    }

    @Override
    public int available() {
        return 0; // TODO
    }

    @Override
    public void close() throws IOException {
        cleanup();
        super.close();
    }

    @Override
    public synchronized void mark(int readlimit) {
        // Do nothing
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public int read() throws IOException {
        read(mTmpBuffer, 0, 1);
        return 0xFF & mTmpBuffer[0];
    }

    @Override
    public int read(byte[] buffer) throws IOException {
        return read(buffer, 0, buffer.length);
    }

    @Override
    public int read(byte[] buffer, int offset, int count) throws IOException {
        if (offset < 0 || count < 0 || (offset + count) > buffer.length) {
            throw new ArrayIndexOutOfBoundsException(String.format(
                    " buffer length %d, offset %d, length %d",
                    buffer.length, offset, count));
        }
        if (!mValidConfig) {
            return 0;
        }
        applyConfigChange();
        int flag = JpegConfig.J_ERROR_FATAL;
        try {
            flag = readDecodedBytes(buffer, offset, count);
        } finally {
            if (flag < 0) {
                cleanup();
            }
        }
        if (flag < 0) {
            switch (flag) {
                case JpegConfig.J_DONE:
                    return -1; // Returns -1 after reading EOS.
                default:
                    throw new IOException("Error reading jpeg stream");
            }
        }
        return flag;
    }

    @Override
    public synchronized void reset() throws IOException {
        throw new IOException("Reset not supported.");
    }

    @Override
    public long skip(long byteCount) throws IOException {
        if (byteCount <= 0) {
            return 0;
        }
        // Shorten skip to a reasonable amount
        int flag = skipDecodedBytes((int) (0x7FFFFFFF & byteCount));
        if (flag < 0) {
            switch (flag) {
                case JpegConfig.J_DONE:
                    return 0; // Returns 0 after reading EOS.
                default:
                    throw new IOException("Error skipping jpeg stream");
            }
        }
        return flag;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            cleanup();
        } finally {
            super.finalize();
        }
    }

    private void applyConfigChange() throws IOException {
        if (mConfigChanged) {
            cleanup();
            Point dimens = new Point(0, 0);
            int flag = setup(dimens, in, mFormat);
            switch(flag) {
                case JpegConfig.J_SUCCESS:
                    break; // allow setup to continue
                case JpegConfig.J_ERROR_BAD_ARGS:
                    throw new IllegalArgumentException("Bad arguments to read");
                default:
                    throw new IOException("Error to reading jpeg headers.");
            }
            mWidth = dimens.x;
            mHeight = dimens.y;
            mConfigChanged = false;
        }
    }

    native private int setup(Point dimens, InputStream in, int format);

    native private void cleanup();

    native private int readDecodedBytes( byte[] inBuffer, int offset, int inCount);

    native private int skipDecodedBytes(int bytes);

    static {
        System.loadLibrary("jni_jpegstream");
    }
}
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.jpegstream;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class JPEGOutputStream extends FilterOutputStream {
    private long JNIPointer = 0; // Used by JNI code. Don't touch.

    private byte[] mTmpBuffer = new byte[1];
    private int mWidth = 0;
    private int mHeight = 0;
    private int mQuality = 0;
    private int mFormat = -1;
    private boolean mValidConfig = false;
    private boolean mConfigChanged = false;

    public JPEGOutputStream(OutputStream out) {
        super(out);
    }

    public JPEGOutputStream(OutputStream out, int width, int height, int quality,
            int format) {
        super(out);
        setConfig(width, height, quality, format);
    }

    public boolean setConfig(int width, int height, int quality, int format) {
        // Clamp quality to range (0, 100]
        quality = Math.max(Math.min(quality, 100), 1);

        // Make sure format is valid
        switch (format) {
            case JpegConfig.FORMAT_GRAYSCALE:
            case JpegConfig.FORMAT_RGB:
            case JpegConfig.FORMAT_ABGR:
            case JpegConfig.FORMAT_RGBA:
                break;
            default:
                return false;
        }

        // If valid, set configuration
        if (width > 0 && height > 0) {
            mWidth = width;
            mHeight = height;
            mFormat = format;
            mQuality = quality;
            mValidConfig = true;
            mConfigChanged = true;
        } else {
            return false;
        }

        return mValidConfig;
    }

    @Override
    public void close() throws IOException {
        cleanup();
        super.close();
    }

    @Override
    public void write(byte[] buffer, int offset, int length) throws IOException {
        if (offset < 0 || length < 0 || (offset + length) > buffer.length) {
            throw new ArrayIndexOutOfBoundsException(String.format(
                    " buffer length %d, offset %d, length %d",
                    buffer.length, offset, length));
        }
        if (!mValidConfig) {
            return;
        }
        if (mConfigChanged) {
            cleanup();
            int flag = setup(out, mWidth, mHeight, mFormat, mQuality);
            switch(flag) {
                case JpegConfig.J_SUCCESS:
                    break; // allow setup to continue
                case JpegConfig.J_ERROR_BAD_ARGS:
                    throw new IllegalArgumentException("Bad arguments to write");
                default:
                    throw new IOException("Error to writing jpeg headers.");
            }
            mConfigChanged = false;
        }
        int returnCode = JpegConfig.J_ERROR_FATAL;
        try {
            returnCode = writeInputBytes(buffer, offset, length);
        } finally {
            if (returnCode < 0) {
                cleanup();
            }
        }
        if (returnCode < 0) {
            throw new IOException("Error writing jpeg stream");
        }
    }

    @Override
    public void write(byte[] buffer) throws IOException {
        write(buffer, 0, buffer.length);
    }

    @Override
    public void write(int oneByte) throws IOException {
        mTmpBuffer[0] = (byte) oneByte;
        write(mTmpBuffer);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            cleanup();
        } finally {
            super.finalize();
        }
    }

    native private int setup(OutputStream out, int width, int height, int format, int quality);

    native private void cleanup();

    native private int writeInputBytes(byte[] inBuffer, int offset, int inCount);

    static {
        System.loadLibrary("jni_jpegstream");
    }
}
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.jpegstream;

public interface JpegConfig {
    // Pixel formats
    public static final int FORMAT_GRAYSCALE = 0x001; // 1 byte/pixel
    public static final int FORMAT_RGB = 0x003; // 3 bytes/pixel RGBRGBRGBRGB...
    public static final int FORMAT_RGBA = 0x004; // 4 bytes/pixel RGBARGBARGBARGBA...
    public static final int FORMAT_ABGR = 0x104; // 4 bytes/pixel ABGRABGRABGR...

    // Jni error codes
    static final int J_SUCCESS = 0;
    static final int J_ERROR_FATAL = -1;
    static final int J_ERROR_BAD_ARGS = -2;
    static final int J_EXCEPTION = -3;
    static final int J_DONE = -4;
}
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.jpegstream;

import java.nio.ByteOrder;

public class StreamUtils {

    private StreamUtils() {
    }

    /**
     * Copies the input byte array into the output int array with the given
     * endianness. If input is not a multiple of 4, ignores the last 1-3 bytes
     * and returns true.
     */
    public static boolean byteToIntArray(int[] output, byte[] input, ByteOrder endianness) {
        int length = input.length - (input.length % 4);
        if (output.length * 4 < length) {
            throw new ArrayIndexOutOfBoundsException("Output array is too short to hold input");
        }
        if (endianness == ByteOrder.BIG_ENDIAN) {
            for (int i = 0, j = 0; i < output.length; i++, j += 4) {
                output[i] = ((input[j] & 0xFF) << 24) | ((input[j + 1] & 0xFF) << 16)
                        | ((input[j + 2] & 0xFF) << 8) | ((input[j + 3] & 0xFF));
            }
        } else {
            for (int i = 0, j = 0; i < output.length; i++, j += 4) {
                output[i] = ((input[j + 3] & 0xFF) << 24) | ((input[j + 2] & 0xFF) << 16)
                        | ((input[j + 1] & 0xFF) << 8) | ((input[j] & 0xFF));
            }
        }
        return input.length % 4 != 0;
    }

    public static int[] byteToIntArray(byte[] input, ByteOrder endianness) {
        int[] output = new int[input.length / 4];
        byteToIntArray(output, input, endianness);
        return output;
    }

    /**
     * Uses native endianness.
     */
    public static int[] byteToIntArray(byte[] input) {
        return byteToIntArray(input, ByteOrder.nativeOrder());
    }

    /**
     * Returns the number of bytes in a pixel for a given format defined in
     * JpegConfig.
     */
    public static int pixelSize(int format) {
        switch (format) {
            case JpegConfig.FORMAT_ABGR:
            case JpegConfig.FORMAT_RGBA:
                return 4;
            case JpegConfig.FORMAT_RGB:
                return 3;
            case JpegConfig.FORMAT_GRAYSCALE:
                return 1;
            default:
                return -1;
        }
    }
}
Loading