Unverified Commit ca6bd907 authored by Oleksandr Karpovich's avatar Oleksandr Karpovich Committed by GitHub

Remove typealias for bytebuffer, replace it with NativePointers or Pixmap where appropriate (#308)

* remove ByteBuffer typealias and use NativePointer instead

* after-rebase changes

* prevent GC from cleaning up underlying memory for Data and Pixmap until they get cleaned by themselves

* skiko Data: make underlyingMemoryOwner mandatory when using makeWithoutCopy
Co-authored-by: 's avatarOleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
parent 21d7595e
...@@ -7,6 +7,13 @@ import org.jetbrains.skia.impl.Library.Companion.staticLoad ...@@ -7,6 +7,13 @@ import org.jetbrains.skia.impl.Library.Companion.staticLoad
* Data holds an immutable data buffer. * Data holds an immutable data buffer.
*/ */
class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHolder.PTR) { class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHolder.PTR) {
/**
* A reference to the underlying memory owner to prevent it from being cleaned by GC until Data instance finalization.
* It's used in [makeWithoutCopy].
*/
private var underlyingMemoryOwner: Managed? = null
companion object { companion object {
fun makeFromBytes(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size): Data { fun makeFromBytes(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size): Data {
Stats.onNativeCall() Stats.onNativeCall()
...@@ -17,6 +24,24 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol ...@@ -17,6 +24,24 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol
) )
} }
/**
* makeWithoutCopy uses NoopReleaseProc as a ReleaseProc, therefore memory needs to be cleaned up by caller side
* (or by [underlyingMemoryOwner] if provided).
*
* @param underlyingMemoryOwner - is stored in Data instance to prevent underlying memory getting cleaned by GC in cases when
* [underlyingMemoryOwner] doesn't have any other references.
*/
fun makeWithoutCopy(memoryAddr: NativePointer, length: Int, underlyingMemoryOwner: Managed): Data {
Stats.onNativeCall()
return Data(
interopScope {
_nMakeWithoutCopy(memoryAddr, length)
}
).also {
it.underlyingMemoryOwner = underlyingMemoryOwner
}
}
/** /**
* Returns a new empty dataref (or a reference to a shared empty dataref). * Returns a new empty dataref (or a reference to a shared empty dataref).
* New or shared, the caller must see that [.close] is eventually called. * New or shared, the caller must see that [.close] is eventually called.
...@@ -26,6 +51,11 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol ...@@ -26,6 +51,11 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol
return Data(_nMakeEmpty()) return Data(_nMakeEmpty())
} }
fun makeUninitialized(length: Int): Data {
Stats.onNativeCall()
return Data(_nMakeUninitialized(length))
}
init { init {
staticLoad() staticLoad()
} }
...@@ -96,10 +126,10 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol ...@@ -96,10 +126,10 @@ class Data internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHol
} }
} }
fun toByteBuffer(): ByteBuffer { fun writableData(): NativePointer {
return try { return try {
Stats.onNativeCall() Stats.onNativeCall()
_nToByteBuffer(_ptr) _nWritableData(_ptr)
} finally { } finally {
reachabilityBarrier(this) reachabilityBarrier(this)
} }
...@@ -123,12 +153,12 @@ private external fun _nBytes(ptr: NativePointer, offset: Int, length: Int, destB ...@@ -123,12 +153,12 @@ private external fun _nBytes(ptr: NativePointer, offset: Int, length: Int, destB
@ExternalSymbolName("org_jetbrains_skia_Data__1nEquals") @ExternalSymbolName("org_jetbrains_skia_Data__1nEquals")
private external fun _nEquals(ptr: NativePointer, otherPtr: NativePointer): Boolean private external fun _nEquals(ptr: NativePointer, otherPtr: NativePointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_Data__1nToByteBuffer")
private external fun _nToByteBuffer(ptr: NativePointer): ByteBuffer
@ExternalSymbolName("org_jetbrains_skia_Data__1nMakeFromBytes") @ExternalSymbolName("org_jetbrains_skia_Data__1nMakeFromBytes")
private external fun _nMakeFromBytes(bytes: InteropPointer, offset: Int, length: Int): NativePointer private external fun _nMakeFromBytes(bytes: InteropPointer, offset: Int, length: Int): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Data__1nMakeWithoutCopy")
private external fun _nMakeWithoutCopy(memoryAddr: NativePointer, length: Int): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Data__1nMakeFromFileName") @ExternalSymbolName("org_jetbrains_skia_Data__1nMakeFromFileName")
internal external fun _nMakeFromFileName(path: InteropPointer): NativePointer internal external fun _nMakeFromFileName(path: InteropPointer): NativePointer
...@@ -137,3 +167,9 @@ private external fun _nMakeSubset(ptr: NativePointer, offset: Int, length: Int): ...@@ -137,3 +167,9 @@ private external fun _nMakeSubset(ptr: NativePointer, offset: Int, length: Int):
@ExternalSymbolName("org_jetbrains_skia_Data__1nMakeEmpty") @ExternalSymbolName("org_jetbrains_skia_Data__1nMakeEmpty")
private external fun _nMakeEmpty(): NativePointer private external fun _nMakeEmpty(): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Data__1nMakeUninitialized")
private external fun _nMakeUninitialized(length: Int): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Data__1nWritableData")
private external fun _nWritableData(dataPtr: NativePointer): NativePointer
package org.jetbrains.skia package org.jetbrains.skia
expect abstract class ByteBuffer
expect fun <R> commonSynchronized(lock: Any, block: () -> R) expect fun <R> commonSynchronized(lock: Any, block: () -> R)
expect fun String.intCodePoints(): IntArray expect fun String.intCodePoints(): IntArray
......
...@@ -246,17 +246,19 @@ class Image internal constructor(ptr: NativePointer) : RefCnt(ptr), IHasImageInf ...@@ -246,17 +246,19 @@ class Image internal constructor(ptr: NativePointer) : RefCnt(ptr), IHasImageInf
} }
/** /**
* If pixel address is available, return ByteBuffer wrapping it. * If pixel address is available, return [Pixmap].
* If pixel address is not available, return null. * If pixel address is not available, return null.
* *
* @return ByteBuffer with direct access to pixels, or null
*
* @see [https://fiddle.skia.org/c/@Image_peekPixels](https://fiddle.skia.org/c/@Image_peekPixels) * @see [https://fiddle.skia.org/c/@Image_peekPixels](https://fiddle.skia.org/c/@Image_peekPixels)
*/ */
fun peekPixels(): ByteBuffer? { fun peekPixels(): Pixmap? {
return try { return try {
Stats.onNativeCall() Stats.onNativeCall()
Image_nPeekPixels(_ptr) Image_nPeekPixels(_ptr).takeIf {
it != NullPointer
}?.let {
Pixmap(it, true)
}
} finally { } finally {
reachabilityBarrier(this) reachabilityBarrier(this)
} }
...@@ -376,7 +378,7 @@ private external fun Image_nGetImageInfo(ptr: NativePointer, imageInfo: InteropP ...@@ -376,7 +378,7 @@ private external fun Image_nGetImageInfo(ptr: NativePointer, imageInfo: InteropP
private external fun Image_nMakeShader(ptr: NativePointer, tmx: Int, tmy: Int, samplingMode: Long, localMatrix: InteropPointer): NativePointer private external fun Image_nMakeShader(ptr: NativePointer, tmx: Int, tmy: Int, samplingMode: Long, localMatrix: InteropPointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nPeekPixels") @ExternalSymbolName("org_jetbrains_skia_Image__1nPeekPixels")
private external fun Image_nPeekPixels(ptr: NativePointer): ByteBuffer? private external fun Image_nPeekPixels(ptr: NativePointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nMakeRaster") @ExternalSymbolName("org_jetbrains_skia_Image__1nMakeRaster")
private external fun _nMakeRaster( private external fun _nMakeRaster(
......
...@@ -4,6 +4,13 @@ import org.jetbrains.skia.impl.* ...@@ -4,6 +4,13 @@ import org.jetbrains.skia.impl.*
class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
Managed(ptr, _FinalizerHolder.PTR, managed) { Managed(ptr, _FinalizerHolder.PTR, managed) {
/**
* A reference to the underlying memory to prevent it from being cleaned up by GC.
* It's used in [reset] and [make].
*/
private var underlyingMemoryOwner: Managed? = null
constructor() : this(_nMakeNull(), true) { constructor() : this(_nMakeNull(), true) {
Stats.onNativeCall() Stats.onNativeCall()
} }
...@@ -11,10 +18,11 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : ...@@ -11,10 +18,11 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
fun reset() { fun reset() {
Stats.onNativeCall() Stats.onNativeCall()
Pixmap_nReset(_ptr) Pixmap_nReset(_ptr)
underlyingMemoryOwner = null
reachabilityBarrier(this) reachabilityBarrier(this)
} }
fun reset(info: ImageInfo, addr: NativePointer, rowBytes: Int) { fun reset(info: ImageInfo, addr: NativePointer, rowBytes: Int, underlyingMemoryOwner: Managed? = null) {
Stats.onNativeCall() Stats.onNativeCall()
_nResetWithInfo( _nResetWithInfo(
_ptr, _ptr,
...@@ -23,12 +31,13 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : ...@@ -23,12 +31,13 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
info.colorInfo.alphaType.ordinal, info.colorInfo.alphaType.ordinal,
getPtr(info.colorInfo.colorSpace), addr, rowBytes getPtr(info.colorInfo.colorSpace), addr, rowBytes
) )
this.underlyingMemoryOwner = underlyingMemoryOwner
reachabilityBarrier(this) reachabilityBarrier(this)
reachabilityBarrier(info.colorInfo.colorSpace) reachabilityBarrier(info.colorInfo.colorSpace)
} }
fun reset(info: ImageInfo, buffer: ByteBuffer, rowBytes: Int) { fun reset(info: ImageInfo, buffer: Data, rowBytes: Int) {
reset(info, BufferUtil.getPointerFromByteBuffer(buffer), rowBytes) reset(info = info, addr = buffer.writableData(), rowBytes = rowBytes, underlyingMemoryOwner = buffer)
} }
fun setColorSpace(colorSpace: ColorSpace?) { fun setColorSpace(colorSpace: ColorSpace?) {
...@@ -54,8 +63,8 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : ...@@ -54,8 +63,8 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
} }
} }
fun extractSubset(buffer: ByteBuffer, area: IRect): Boolean { fun extractSubset(subset: Pixmap, area: IRect): Boolean {
return extractSubset(BufferUtil.getPointerFromByteBuffer(buffer), area) return extractSubset(subset._ptr, area)
} }
val info: ImageInfo val info: ImageInfo
...@@ -240,19 +249,23 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : ...@@ -240,19 +249,23 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
} }
} }
val buffer: ByteBuffer? val buffer: Data
get() = BufferUtil.getByteBufferFromPointer(addr, computeByteSize()) get() = (underlyingMemoryOwner as? Data) ?: Data.makeWithoutCopy(
memoryAddr = addr,
length = computeByteSize(),
underlyingMemoryOwner = this
)
private object _FinalizerHolder { private object _FinalizerHolder {
val PTR = Pixmap_nGetFinalizer() val PTR = Pixmap_nGetFinalizer()
} }
companion object { companion object {
fun make(info: ImageInfo, buffer: ByteBuffer, rowBytes: Int): Pixmap { fun make(info: ImageInfo, buffer: Data, rowBytes: Int): Pixmap {
return make(info, BufferUtil.getPointerFromByteBuffer(buffer), rowBytes) return make(info, buffer.writableData(), rowBytes, underlyingMemoryOwner = buffer)
} }
fun make(info: ImageInfo, addr: NativePointer, rowBytes: Int): Pixmap { fun make(info: ImageInfo, addr: NativePointer, rowBytes: Int, underlyingMemoryOwner: Managed? = null): Pixmap {
return try { return try {
val ptr = Pixmap_nMake( val ptr = Pixmap_nMake(
info.width, info.height, info.width, info.height,
...@@ -261,7 +274,9 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) : ...@@ -261,7 +274,9 @@ class Pixmap internal constructor(ptr: NativePointer, managed: Boolean) :
getPtr(info.colorInfo.colorSpace), addr, rowBytes getPtr(info.colorInfo.colorSpace), addr, rowBytes
) )
require(ptr != NullPointer) { "Failed to create Pixmap." } require(ptr != NullPointer) { "Failed to create Pixmap." }
Pixmap(ptr, true) Pixmap(ptr, true).also {
it.underlyingMemoryOwner = underlyingMemoryOwner
}
} finally { } finally {
reachabilityBarrier(info.colorInfo.colorSpace) reachabilityBarrier(info.colorInfo.colorSpace)
} }
...@@ -390,4 +405,4 @@ private external fun _nEraseSubset( ...@@ -390,4 +405,4 @@ private external fun _nEraseSubset(
t: Int, t: Int,
r: Int, r: Int,
b: Int b: Int
): Boolean // TODO float erase methods not included ): Boolean // TODO float erase methods not included
\ No newline at end of file
...@@ -113,4 +113,21 @@ class BitmapTest { ...@@ -113,4 +113,21 @@ class BitmapTest {
assertEquals(setArray.size, result.size) assertEquals(setArray.size, result.size)
assertContentEquals(setArray, result) assertContentEquals(setArray, result)
} }
@Test
fun canPeekPixelsAndCompare() = runTest {
val bitmap = Bitmap()
bitmap.allocPixels(ImageInfo.makeS32(2, 2, ColorAlphaType.OPAQUE))
val setArray = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
assertTrue(bitmap.installPixels(setArray))
val dataBytes = bitmap.peekPixels()!!.buffer.bytes
assertEquals(setArray.size, dataBytes.size)
setArray.forEachIndexed { ix, value ->
assertEquals(value, dataBytes[ix])
}
}
} }
package org.jetbrains.skia package org.jetbrains.skia
import org.jetbrains.skia.impl.Native.Companion.NullPointer
import org.jetbrains.skia.impl.use import org.jetbrains.skia.impl.use
import org.jetbrains.skiko.tests.runTest import org.jetbrains.skiko.tests.runTest
import kotlin.test.* import kotlin.test.*
...@@ -63,4 +64,58 @@ class DataTest { ...@@ -63,4 +64,58 @@ class DataTest {
assertContentEquals(byteArrayOf(), it.getBytes(5, 0)) assertContentEquals(byteArrayOf(), it.getBytes(5, 0))
} }
} }
@Test
fun memorySuppliedToMakeDataWithoutCopyDoesntGetCleanedUp() = runTest {
val bytes = byteArrayOf(1, 2, 3, 4, 5)
val originalData = Data.makeFromBytes(bytes)
val originalDataPtr = originalData._ptr
// `use()` calls `close()` in the end
val copiedData = Data.makeWithoutCopy(
memoryAddr = originalData.writableData(),
length = 5,
underlyingMemoryOwner = originalData
).use {
assertNotEquals(NullPointer, it._ptr)
assertContentEquals(bytes, it.bytes)
it
}
assertEquals(NullPointer, copiedData._ptr)
assertEquals(originalDataPtr, originalData._ptr)
assertContentEquals(
bytes,
originalData.bytes,
message = "Memory is not expected to get cleaned up because Data.makeWithoutCopy uses NoopReleaseProc as a ReleaseProc"
)
}
@Test
fun canMakeUninitialized() = runTest {
val data = Data.makeUninitialized(100)
assertEquals(100, data.bytes.size)
println(data.bytes.joinToString())
val imageInfo = ImageInfo.makeN32Premul(5, 5)
val pixmap = Pixmap.make(imageInfo, data, imageInfo.minRowBytes)
val surface = Surface.makeRasterDirect(pixmap)
surface.canvas.clear(Color.WHITE)
// check that data contains all white pixels
assertContentEquals(
expected = ByteArray(100) { -1 },
actual = data.bytes
)
surface.canvas.clear(Color.BLACK)
// check that data contains all black pixels
assertContentEquals(
// every 4th byte is -1, the rest is 0 for Color.BLACK
expected = ByteArray(100) { if ((it + 1) % 4 == 0) -1 else 0 },
actual = data.bytes
)
}
} }
...@@ -3,6 +3,7 @@ package org.jetbrains.skia ...@@ -3,6 +3,7 @@ package org.jetbrains.skia
import org.jetbrains.skia.impl.use import org.jetbrains.skia.impl.use
import org.jetbrains.skiko.tests.runTest import org.jetbrains.skiko.tests.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
...@@ -69,6 +70,28 @@ class ImageTest { ...@@ -69,6 +70,28 @@ class ImageTest {
val image = Image.makeRaster(imageInfo, bytes, 5) val image = Image.makeRaster(imageInfo, bytes, 5)
} }
@Test
fun canPeekPixels() = runTest {
val bytes = intArrayOf(
0xCA, 0xDA, 0xCA, 0xC9, 0xA3,
0xAC, 0xA8, 0x89, 0xA7, 0x87,
0x9B, 0xB5, 0xE5, 0x95, 0x46,
0x90, 0x81, 0xC5, 0x71, 0x33,
0x75, 0x55, 0x44, 0x40, 0x30
).map { it.toByte() }.toByteArray()
val imageInfo = ImageInfo(
width = 5, height = 5,
colorType = ColorType.GRAY_8,
alphaType = ColorAlphaType.OPAQUE
)
val image = Image.makeRaster(imageInfo, bytes, 5)
val pixels = image.peekPixels()!!.buffer.bytes
assertContentEquals(bytes, pixels)
}
private fun <T> imageUnderTest(block: (Image) -> T): T { private fun <T> imageUnderTest(block: (Image) -> T): T {
return Surface.makeRasterN32Premul(100, 100).use { surface -> return Surface.makeRasterN32Premul(100, 100).use { surface ->
val paint = Paint() val paint = Paint()
......
package org.jetbrains.skiko.tests package org.jetbrains.skiko.tests
import org.jetbrains.skia.ByteBuffer
import org.jetbrains.skia.ExternalSymbolName import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.* import org.jetbrains.skia.impl.*
...@@ -36,12 +35,6 @@ class TestHelpers { ...@@ -36,12 +35,6 @@ class TestHelpers {
} }
} }
fun getPointerFromByteBuffer(buffer: ByteBuffer): NativePointer {
val result = _nGetPointerFromByteBuffer(buffer)
require(result != Native.NullPointer) { "The given buffer " + buffer + "is not a direct buffer or current JVM doesn't support JNI direct buffer access!" }
return result
}
init { init {
Library.staticLoad() Library.staticLoad()
} }
...@@ -61,6 +54,3 @@ private external fun _nFillIntArrayOf5(interopPointer: InteropPointer) ...@@ -61,6 +54,3 @@ private external fun _nFillIntArrayOf5(interopPointer: InteropPointer)
@ExternalSymbolName("org_jetbrains_skiko_tests_TestHelpers__1nFillDoubleArrayOf5") @ExternalSymbolName("org_jetbrains_skiko_tests_TestHelpers__1nFillDoubleArrayOf5")
private external fun _nFillDoubleArrayOf5(interopPointer: InteropPointer) private external fun _nFillDoubleArrayOf5(interopPointer: InteropPointer)
@ExternalSymbolName("org_jetbrains_skiko_tests_TestHelpers__1nGetPointerFromByteBuffer")
private external fun _nGetPointerFromByteBuffer(buffer: ByteBuffer?): NativePointer
package org.jetbrains.skia package org.jetbrains.skia
actual abstract class OutputStream actual abstract class OutputStream
actual abstract class ByteBuffer
actual fun <R> commonSynchronized(lock: Any, block: () -> R) { actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
block() block()
......
...@@ -46,6 +46,13 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeFromByt ...@@ -46,6 +46,13 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeFromByt
return reinterpret_cast<jlong>(instance); return reinterpret_cast<jlong>(instance);
} }
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeWithoutCopy
(JNIEnv* env, jclass jclass, jlong memoryAddr, jint length) {
SkData* instance = SkData::MakeWithoutCopy(reinterpret_cast<void*>(memoryAddr), length).release();
return reinterpret_cast<jlong>(instance);
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeFromFileName extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeFromFileName
(JNIEnv* env, jclass jclass, jstring pathStr) { (JNIEnv* env, jclass jclass, jstring pathStr) {
SkString path = skString(env, pathStr); SkString path = skString(env, pathStr);
...@@ -65,3 +72,15 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeEmpty ...@@ -65,3 +72,15 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeEmpty
SkData* instance = SkData::MakeEmpty().release(); SkData* instance = SkData::MakeEmpty().release();
return reinterpret_cast<jlong>(instance); return reinterpret_cast<jlong>(instance);
} }
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nMakeUninitialized
(JNIEnv* env, jclass jclass, jint length) {
SkData* instance = SkData::MakeUninitialized(length).release();
return reinterpret_cast<jlong>(instance);
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DataKt__1nWritableData
(JNIEnv* env, jclass jclass, jlong ptr) {
SkData* instance = reinterpret_cast<SkData*>(static_cast<uintptr_t>(ptr));
return reinterpret_cast<jlong>(instance->writable_data());
}
...@@ -81,14 +81,17 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ImageKt_Image_1nMakeS ...@@ -81,14 +81,17 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ImageKt_Image_1nMakeS
return reinterpret_cast<jlong>(shader.release()); return reinterpret_cast<jlong>(shader.release());
} }
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_ImageKt_Image_1nPeekPixels
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ImageKt_Image_1nPeekPixels
(JNIEnv* env, jclass jclass, jlong ptr) { (JNIEnv* env, jclass jclass, jlong ptr) {
SkImage* instance = reinterpret_cast<SkImage*>(static_cast<uintptr_t>(ptr)); SkImage* instance = reinterpret_cast<SkImage*>(static_cast<uintptr_t>(ptr));
SkPixmap pixmap; SkPixmap* pixmap = new SkPixmap();
if (instance->peekPixels(&pixmap)) if (instance->peekPixels(pixmap))
return env->NewDirectByteBuffer(pixmap.writable_addr(), pixmap.rowBytes() * pixmap.height()); return ptrToJlong(pixmap);
else else {
return nullptr; delete pixmap;
return 0;
}
} }
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_ImageKt__1nPeekPixelsToPixmap extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_ImageKt__1nPeekPixelsToPixmap
......
...@@ -2,8 +2,6 @@ package org.jetbrains.skia ...@@ -2,8 +2,6 @@ package org.jetbrains.skia
import java.util.* import java.util.*
actual typealias ByteBuffer = java.nio.ByteBuffer
actual fun <R> commonSynchronized(lock: Any, block: () -> R) { actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
synchronized(lock, block) synchronized(lock, block)
} }
......
package org.jetbrains.skia.impl package org.jetbrains.skia.impl
import org.jetbrains.skia.ByteBuffer import java.nio.ByteBuffer
import org.jetbrains.skia.ExternalSymbolName
object BufferUtil { object BufferUtil {
fun getByteBufferFromPointer(ptr: NativePointer, size: Int): ByteBuffer { fun getByteBufferFromPointer(ptr: NativePointer, size: Int): ByteBuffer {
...@@ -16,9 +15,6 @@ object BufferUtil { ...@@ -16,9 +15,6 @@ object BufferUtil {
} }
} }
@ExternalSymbolName("org_jetbrains_skia_BufferUtil__1nGetByteBufferFromPointer")
private external fun _nGetByteBufferFromPointer(ptr: NativePointer, size: Int): ByteBuffer? private external fun _nGetByteBufferFromPointer(ptr: NativePointer, size: Int): ByteBuffer?
@ExternalSymbolName("org_jetbrains_skia_BufferUtil__1nGetPointerFromByteBuffer") private external fun _nGetPointerFromByteBuffer(buffer: ByteBuffer?): NativePointer
private external fun _nGetPointerFromByteBuffer(buffer: ByteBuffer?): NativePointer
\ No newline at end of file
...@@ -5,6 +5,7 @@ import org.jetbrains.skia.ColorAlphaType ...@@ -5,6 +5,7 @@ import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorType import org.jetbrains.skia.ColorType
import org.jetbrains.skia.ImageInfo import org.jetbrains.skia.ImageInfo
import org.jetbrains.skia.Image import org.jetbrains.skia.Image
import org.jetbrains.skia.impl.BufferUtil
import java.awt.Transparency import java.awt.Transparency
import java.awt.color.ColorSpace import java.awt.color.ColorSpace
import java.awt.image.* import java.awt.image.*
...@@ -19,6 +20,33 @@ private class DirectDataBuffer(val backing: ByteBuffer): DataBuffer(TYPE_BYTE, b ...@@ -19,6 +20,33 @@ private class DirectDataBuffer(val backing: ByteBuffer): DataBuffer(TYPE_BYTE, b
} }
} }
fun Bitmap.toBufferedImage(): BufferedImage {
val pixelsNativePointer = this.peekPixels()!!.addr
val pixelsBuffer = BufferUtil.getByteBufferFromPointer(pixelsNativePointer, this.rowBytes * this.height)
val order = when (this.colorInfo.colorType) {
ColorType.RGB_888X -> intArrayOf(0, 1, 2, 3)
ColorType.BGRA_8888 -> intArrayOf(2, 1, 0, 3)
else -> throw UnsupportedOperationException("unsupported color type ${this.colorInfo.colorType}")
}
val raster = Raster.createInterleavedRaster(
DirectDataBuffer(pixelsBuffer),
this.width,
this.height,
this.width * 4,
4,
order,
null
)
val colorModel = ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
true,
false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE
)
return BufferedImage(colorModel, raster!!, false, null)
}
fun BufferedImage.toBitmap(): Bitmap { fun BufferedImage.toBitmap(): Bitmap {
val bytesPerPixel = 4 val bytesPerPixel = 4
...@@ -47,4 +75,4 @@ fun BufferedImage.toBitmap(): Bitmap { ...@@ -47,4 +75,4 @@ fun BufferedImage.toBitmap(): Bitmap {
fun BufferedImage.toImage(): Image { fun BufferedImage.toImage(): Image {
return Image.makeFromBitmap(toBitmap()) return Image.makeFromBitmap(toBitmap())
} }
\ No newline at end of file
...@@ -55,9 +55,3 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skiko_tests_TestHelpersKt__ ...@@ -55,9 +55,3 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skiko_tests_TestHelpersKt__
result_double[4] = 10000000.9991; result_double[4] = 10000000.9991;
env->ReleaseDoubleArrayElements(jdarray, result_double, 0); env->ReleaseDoubleArrayElements(jdarray, result_double, 0);
} }
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_tests_TestHelpersKt__1nGetPointerFromByteBuffer
(JNIEnv *env, jclass, jobject buffer) {
return reinterpret_cast<jlong>(env->GetDirectBufferAddress(buffer));
}
package org.jetbrains.skiko.tests package org.jetbrains.skiko.tests
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.jetbrains.skia.impl.BufferUtil
import org.jetbrains.skia.impl.InteropScope import org.jetbrains.skia.impl.InteropScope
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import java.nio.ByteBuffer import java.nio.ByteBuffer
...@@ -12,5 +13,5 @@ actual fun runTest(block: suspend () -> Unit) { ...@@ -12,5 +13,5 @@ actual fun runTest(block: suspend () -> Unit) {
} }
actual fun InteropScope.allocateBytesForPixels(size: Int): NativePointer { actual fun InteropScope.allocateBytesForPixels(size: Int): NativePointer {
return TestHelpers().getPointerFromByteBuffer(ByteBuffer.allocateDirect(size)) return BufferUtil.getPointerFromByteBuffer(ByteBuffer.allocateDirect(size))
} }
package org.jetbrains.skiko.tests.org.jetbrains.skiko
import org.jetbrains.skia.*
import org.jetbrains.skiko.toBitmap
import org.jetbrains.skiko.toBufferedImage
import org.junit.Test
import kotlin.test.assertEquals
class ConvertorsTests {
@Test
fun canConvertBitmapToBufferedImage() {
val bytes = intArrayOf(
0xCA, 0xDA, 0xCA, 0xC9, 0xA3, 0xAC, 0xA8, 0x89,
0x9B, 0xB5, 0xE5, 0x95, 0x46, 0x90, 0x81, 0xC5
).map { it.toByte() }.toByteArray()
val imageInfo = ImageInfo(
width = 2, height = 2,
colorType = ColorType.RGB_888X,
alphaType = ColorAlphaType.OPAQUE
)
val image = Image.makeRaster(imageInfo, bytes, 8)
val bitmap = Bitmap.makeFromImage(image)
val bufferedImage = bitmap.toBufferedImage()
val bitmapConverted = bufferedImage.toBitmap()
val pixels = bitmapConverted.readPixels(imageInfo)!!
assertEquals(bytes.size, pixels.size)
}
}
package org.jetbrains.skiko.util package org.jetbrains.skiko.util
import org.jetbrains.skia.ByteBuffer import org.jetbrains.skia.Data
import org.jetbrains.skia.Image import org.jetbrains.skia.Image
import org.jetbrains.skia.Pixmap import org.jetbrains.skia.Pixmap
import java.awt.Color import java.awt.Color
...@@ -16,12 +16,12 @@ fun isContentSame(img1: Image, img2: Image, sensitivity: Double): Boolean { ...@@ -16,12 +16,12 @@ fun isContentSame(img1: Image, img2: Image, sensitivity: Double): Boolean {
pixMap1.reset( pixMap1.reset(
img1.imageInfo, img1.imageInfo,
ByteBuffer.allocateDirect(img1.bytesPerPixel * img1.width * img1.height), Data.makeUninitialized(img1.bytesPerPixel * img1.width * img1.height),
img1.bytesPerPixel * img1.width img1.bytesPerPixel * img1.width
) )
pixMap2.reset( pixMap2.reset(
img2.imageInfo, img2.imageInfo,
ByteBuffer.allocateDirect(img2.bytesPerPixel * img2.width * img2.height), Data.makeUninitialized(img2.bytesPerPixel * img2.width * img2.height),
img2.bytesPerPixel * img2.width img2.bytesPerPixel * img2.width
) )
check(img1.readPixels(pixMap1, 0, 0, false)) check(img1.readPixels(pixMap1, 0, 0, false))
......
...@@ -19,22 +19,6 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Data__1nSize ...@@ -19,22 +19,6 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Data__1nSize
return instance->size(); return instance->size();
} }
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Data__1nToByteBuffer
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_Data__1nToByteBuffer");
}
#if 0
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Data__1nToByteBuffer
(KNativePointer ptr) {
SkData* instance = reinterpret_cast<SkData*>((ptr));
return env->NewDirectByteBuffer(instance->writable_data(), instance->size());
}
#endif
SKIKO_EXPORT void org_jetbrains_skia_Data__1nBytes SKIKO_EXPORT void org_jetbrains_skia_Data__1nBytes
(KNativePointer ptr, int offset, int length, KByte* destBytes) { (KNativePointer ptr, int offset, int length, KByte* destBytes) {
SkData* instance = reinterpret_cast<SkData*>(ptr); SkData* instance = reinterpret_cast<SkData*>(ptr);
...@@ -58,6 +42,13 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeFromBytes ...@@ -58,6 +42,13 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeFromBytes
return instance; return instance;
} }
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeWithoutCopy
(KNativePointer memoryAddr, KInt length) {
SkData* instance = SkData::MakeWithoutCopy(reinterpret_cast<void*>(memoryAddr), length).release();
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeFromFileName SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeFromFileName
(KInteropPointer pathStr) { (KInteropPointer pathStr) {
SkString path = skString(pathStr); SkString path = skString(pathStr);
...@@ -77,3 +68,14 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeEmpty() { ...@@ -77,3 +68,14 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeEmpty() {
SkData* instance = SkData::MakeEmpty().release(); SkData* instance = SkData::MakeEmpty().release();
return reinterpret_cast<KNativePointer>(instance); return reinterpret_cast<KNativePointer>(instance);
} }
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nMakeUninitialized(KInt length) {
SkData* instance = SkData::MakeUninitialized(length).release();
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Data__1nWritableData
(KNativePointer ptr) {
SkData* instance = reinterpret_cast<SkData*>(ptr);
return reinterpret_cast<KNativePointer>(instance->writable_data());
}
...@@ -84,25 +84,17 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeShader ...@@ -84,25 +84,17 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeShader
return reinterpret_cast<KNativePointer>(shader.release()); return reinterpret_cast<KNativePointer>(shader.release());
} }
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Image__1nPeekPixels
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_Image__1nPeekPixels");
}
#if 0
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Image__1nPeekPixels SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Image__1nPeekPixels
(KNativePointer ptr) { (KNativePointer ptr) {
SkImage* instance = reinterpret_cast<SkImage*>((ptr)); SkImage* instance = reinterpret_cast<SkImage*>(ptr);
SkPixmap pixmap; SkPixmap* pixmap = new SkPixmap();
if (instance->peekPixels(&pixmap)) if (instance->peekPixels(pixmap)) {
return env->NewDirectByteBuffer(pixmap.writable_addr(), pixmap.rowBytes() * pixmap.height()); return reinterpret_cast<KNativePointer>(pixmap);
else } else {
return nullptr; delete pixmap;
return nullptr;
}
} }
#endif
SKIKO_EXPORT KBoolean org_jetbrains_skia_Image__1nPeekPixelsToPixmap SKIKO_EXPORT KBoolean org_jetbrains_skia_Image__1nPeekPixelsToPixmap
(KNativePointer ptr, KNativePointer pixmapPtr) { (KNativePointer ptr, KNativePointer pixmapPtr) {
......
package org.jetbrains.skia package org.jetbrains.skia
actual abstract class ByteBuffer
actual abstract class OutputStream actual abstract class OutputStream
actual fun <R> commonSynchronized(lock: Any, block: () -> R) { actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment