JNIEXPORT void JNICALL Java_org_openpanodroid_panoutils_android_CubicPanoNative_calculateCubeSide(JNIEnv *env, jclass obj, jobject panoBmp, jobject faceBmp, jint face) {

	LOGI("Entering native calculateCubeSide()");

	AndroidBitmapInfo panoBmpInfo;
	AndroidBitmapInfo faceBmpInfo;
	void *panoPixels;
	void *facePixels;
	CubeCoordinates cubeCoordinates;
	SphericalCoordinates sphericalCoordinates;

	if (AndroidBitmap_getInfo(env, panoBmp, &panoBmpInfo) < 0) {
		LOGE("Could not get info for pano bitmap");
		return;
	}

	if (AndroidBitmap_getInfo(env, faceBmp, &faceBmpInfo) < 0) {
		LOGE("Could not get info for face bitmap");
		return;
	}

	if (panoBmpInfo.width != 2*panoBmpInfo.height) {
		LOGE("Invalid pano bitmap size (width != 2*height)");
		return;
	}

	if (faceBmpInfo.width != faceBmpInfo.height) {
		LOGE("Invalid face bitmap size (width != height)");
		return;
	}

	if (faceBmpInfo.format != panoBmpInfo.format) {
		LOGE("Format of pano bitmap and face bitmap not equal");
		return;
	}

	if (panoBmpInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Invalid bitmap format != RGBA_8888");
		return;
	}

	if (AndroidBitmap_lockPixels(env, panoBmp, &panoPixels) < 0) {
		LOGE("Could not lock pixels of pano bitmap");
		return;
	}

	if (AndroidBitmap_lockPixels(env, faceBmp, &facePixels) < 0) {
		LOGE("Could not lock pixels of face bitmap");
		AndroidBitmap_unlockPixels(env, panoBmp);
		return;
	}

	initTextureToCubeCoordinates(faceBmpInfo.width);

	for (int xTexture = 0; xTexture < faceBmpInfo.width; xTexture++) {
		LOGI("Texture row: %d", xTexture);
		for (int yTexture = 0; yTexture < faceBmpInfo.height; yTexture++) {
			textureToCubeCoordinates(xTexture, yTexture, &cubeCoordinates);
			
			cubeToSphericalCoordinates(&cubeCoordinates, face, &sphericalCoordinates);
			
			uint32_t pixel = getEquirectangularPixel(panoPixels, panoBmpInfo.width, panoBmpInfo.height, panoBmpInfo.stride, &sphericalCoordinates);
			
			setPixelRGBA(facePixels, faceBmpInfo.stride, xTexture, yTexture, pixel);
		}
	}

	AndroidBitmap_unlockPixels(env, panoBmp);
	AndroidBitmap_unlockPixels(env, faceBmp);

	LOGI("Leaving native calculateCubeSide()");
}
void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int)
{
    if (m_frameBufferCache.isEmpty())
        return;

    // Initialize the framebuffer if needed.
    ImageFrame& buffer = m_frameBufferCache[0];
    if (buffer.status() == ImageFrame::FrameEmpty) {
        png_structp png = m_reader->pngPtr();
        if (!buffer.setSize(scaledSize().width(), scaledSize().height())) {
            longjmp(JMPBUF(png), 1);
            return;
        }

        unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3;
        if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr())) {
            m_reader->createInterlaceBuffer(colorChannels * size().width() * size().height());
            if (!m_reader->interlaceBuffer()) {
                longjmp(JMPBUF(png), 1);
                return;
            }
        }

#if USE(QCMSLIB)
        if (m_reader->colorTransform()) {
            m_reader->createRowBuffer(colorChannels * size().width());
            if (!m_reader->rowBuffer()) {
                longjmp(JMPBUF(png), 1);
                return;
            }
        }
#endif
        buffer.setStatus(ImageFrame::FramePartial);
        buffer.setHasAlpha(false);
        buffer.setColorProfile(m_colorProfile);

        // For PNGs, the frame always fills the entire image.
        buffer.setOriginalFrameRect(IntRect(IntPoint(), size()));
    }

    /* libpng comments (here to explain what follows).
     *
     * this function is called for every row in the image.  If the
     * image is interlacing, and you turned on the interlace handler,
     * this function will be called for every row in every pass.
     * Some of these rows will not be changed from the previous pass.
     * When the row is not changed, the new_row variable will be NULL.
     * The rows and passes are called in order, so you don't really
     * need the row_num and pass, but I'm supplying them because it
     * may make your life easier.
     */

    // Nothing to do if the row is unchanged, or the row is outside
    // the image bounds: libpng may send extra rows, ignore them to
    // make our lives easier.
    if (!rowBuffer)
        return;
    int y = !m_scaled ? rowIndex : scaledY(rowIndex);
    if (y < 0 || y >= scaledSize().height())
        return;

    /* libpng comments (continued).
     *
     * For the non-NULL rows of interlaced images, you must call
     * png_progressive_combine_row() passing in the row and the
     * old row.  You can call this function for NULL rows (it will
     * just return) and for non-interlaced images (it just does the
     * memcpy for you) if it will make the code easier.  Thus, you
     * can just do this for all cases:
     *
     *    png_progressive_combine_row(png_ptr, old_row, new_row);
     *
     * where old_row is what was displayed for previous rows.  Note
     * that the first pass (pass == 0 really) will completely cover
     * the old row, so the rows do not have to be initialized.  After
     * the first pass (and only for interlaced images), you will have
     * to pass the current row, and the function will combine the
     * old row and the new row.
     */

    bool hasAlpha = m_reader->hasAlpha();
    unsigned colorChannels = hasAlpha ? 4 : 3;
    png_bytep row = rowBuffer;

    if (png_bytep interlaceBuffer = m_reader->interlaceBuffer()) {
        row = interlaceBuffer + (rowIndex * colorChannels * size().width());
        png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer);
    }

#if USE(QCMSLIB)
    if (qcms_transform* transform = m_reader->colorTransform()) {
        qcms_transform_data(transform, row, m_reader->rowBuffer(), size().width());
        row = m_reader->rowBuffer();
    }
#endif

    // Write the decoded row pixels to the frame buffer.
    ImageFrame::PixelData* address = buffer.getAddr(0, y);
    int width = scaledSize().width();
    unsigned char nonTrivialAlphaMask = 0;

#if ENABLE(IMAGE_DECODER_DOWN_SAMPLING)
    if (m_scaled) {
        for (int x = 0; x < width; ++x) {
            png_bytep pixel = row + m_scaledColumns[x] * colorChannels;
            unsigned alpha = hasAlpha ? pixel[3] : 255;
            buffer.setRGBA(address++, pixel[0], pixel[1], pixel[2], alpha);
            nonTrivialAlphaMask |= (255 - alpha);
        }
    } else
#endif
    {
        png_bytep pixel = row;
        if (hasAlpha) {
            if (buffer.premultiplyAlpha()) {
                for (int x = 0; x < width; ++x, pixel += 4)
                    setPixelPremultipliedRGBA(address++, pixel, nonTrivialAlphaMask);
            } else {
                for (int x = 0; x < width; ++x, pixel += 4)
                    setPixelRGBA(address++, pixel, nonTrivialAlphaMask);
            }
        } else {
            for (int x = 0; x < width; ++x, pixel += 3)
                setPixelRGB(address++, pixel);
        }
    }


    if (nonTrivialAlphaMask && !buffer.hasAlpha())
        buffer.setHasAlpha(true);
}