Skip to content

Commit

Permalink
Add a way to make a VBlob from some bytes (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
lopcode authored Oct 6, 2024
1 parent be17098 commit 20fbb2c
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repositories {
}

dependencies {
implementation("app.photofox.vips-ffm:vips-ffm-core:1.0.0")
implementation("app.photofox.vips-ffm:vips-ffm-core:1.1.0")
}
```
When running your project you must add `--enable-native-access=ALL-UNNAMED` to your JVM runtime arguments. If you
Expand Down
20 changes: 19 additions & 1 deletion core/src/main/java/app/photofox/vipsffm/VBlob.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.ByteBuffer;

import static app.photofox.vipsffm.jextract.VipsRaw.C_LONG;
Expand All @@ -28,6 +29,23 @@ public final class VBlob {
this.address = address;
}

/**
* Creates a new VBlob from a fixed array of bytes
* This must copy the data - it's generally more efficient to use {@link VImage#newFromFile(Arena, String, VipsOption...)},
* {@link VImage#newFromSource(Arena, VSource, String, VipsOption...)}, and friends
* @param arena An arena constraining the lifetime of this blob
* @param bytes The bytes to wrap
*/
public static VBlob newFromBytes(Arena arena, byte[] bytes) throws VipsError {
var offHeapSegment = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes);
var blobAddress = VipsRaw.vips_blob_new(MemorySegment.NULL, offHeapSegment, offHeapSegment.byteSize());
if (!VipsValidation.isValidPointer(blobAddress)) {
throw new VipsError("invalid blob returned from libvips");
}
blobAddress.reinterpret(arena, VipsRaw::vips_area_unref);
return new VBlob(arena, blobAddress);
}

/**
* @deprecated Replaced by {@link #getUnsafeStructAddress()}
*/
Expand Down Expand Up @@ -103,7 +121,7 @@ public ByteBuffer asClonedByteBuffer() {
}
buffer.rewind();
var newBuffer = ByteBuffer.allocate(buffer.capacity());
buffer.put(newBuffer);
newBuffer.put(buffer);
newBuffer.rewind();
return newBuffer;
}
Expand Down
23 changes: 23 additions & 0 deletions core/src/main/java/app/photofox/vipsffm/VImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -10358,6 +10358,29 @@ public static VImage newFromSource(Arena arena, VSource source, String optionStr
return outOption.valueOrThrow();
}

public static VImage newFromSource(Arena arena, VSource source, VipsOption... options) throws
VipsError {
return newFromSource(arena, source, "", options);
}

/**
* Creates a new VImage from raw bytes. Note that this is quite inefficient, use {@link VImage#newFromFile(Arena, String, VipsOption...)} and friends instead.
*/
public static VImage newFromBytes(Arena arena, byte[] bytes, String optionString,
VipsOption... options) throws VipsError {
var source = VSource.newFromBytes(arena, bytes);
return newFromSource(arena, source, optionString, options);
}

/**
* See {@link VImage#newFromBytes(Arena, byte[], String, VipsOption...)}
*/
public static VImage newFromBytes(Arena arena, byte[] bytes, VipsOption... options) throws
VipsError {
var source = VSource.newFromBytes(arena, bytes);
return newFromSource(arena, source, options);
}

public void writeToFile(String path, VipsOption... options) throws VipsError {
var filename = VipsHelper.filename_get_filename(arena, path);
var filenameOptions = VipsHelper.filename_get_options(arena, filename);
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/app/photofox/vipsffm/VSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,40 @@ public MemorySegment getUnsafeStructAddress() {
return this.address;
}

/**
* Create a new VSource from a file descriptor
*/
public static VSource newFromDescriptor(Arena arena, int descriptor) throws VipsError {
var pointer = VipsHelper.source_new_from_descriptor(arena, descriptor);
return new VSource(pointer);
}

/**
* Create a new VSource from a file path
*/
public static VSource newFromFile(Arena arena, String filename) throws VipsError {
var pointer = VipsHelper.source_new_from_file(arena, filename);
return new VSource(pointer);
}

/**
* Create a new VSource from a VBlob, usually received from a Vips operation
*/
public static VSource newFromBlob(Arena arena, VBlob blob) throws VipsError {
var pointer = VipsHelper.source_new_from_blob(arena, blob.address);
return new VSource(pointer);
}

/**
* Create a new VSource directly from some bytes
* Note that this makes a full copy of the data, which is inefficient - prefer {@link VImage#newFromFile(Arena, String, VipsOption...)}
* and friends
*/
public static VSource newFromBytes(Arena arena, byte[] bytes) throws VipsError {
var blob = VBlob.newFromBytes(arena, bytes);
return newFromBlob(arena, blob);
}

public static VSource newFromOptions(Arena arena, String options) throws VipsError {
var pointer = VipsHelper.source_new_from_options(arena, options);
return new VSource(pointer);
Expand Down
38 changes: 38 additions & 0 deletions generator/src/main/kotlin/vipsffm/GenerateVClasses.kt
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,41 @@ object GenerateVClasses {
.addStatement("\$T.invokeOperation(arena, loader, optionString, callArgs)", vipsInvokerType)
.addStatement("return outOption.valueOrThrow()")
.build()
val newFromSourceNoOptionsMethod = MethodSpec.methodBuilder("newFromSource")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(arenaType, "arena")
.addParameter(vsourceType, "source")
.addParameter(vipsOptionVarargType, "options")
.returns(vimageType)
.varargs(true)
.addException(vipsErrorType)
.addStatement("return newFromSource(arena, source, \"\", options)")
.build()
val newFromBytesMethod = MethodSpec.methodBuilder("newFromBytes")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(arenaType, "arena")
.addParameter(ArrayTypeName.of(TypeName.BYTE), "bytes")
.addParameter(stringType, "optionString")
.addParameter(vipsOptionVarargType, "options")
.returns(vimageType)
.varargs(true)
.addException(vipsErrorType)
.addStatement("var source = \$T.newFromBytes(arena, bytes)", vsourceType)
.addStatement("return newFromSource(arena, source, optionString, options)", vipsOptionSourceType)
.addJavadoc("Creates a new VImage from raw bytes. Note that this is quite inefficient, use {@link VImage#newFromFile(Arena, String, VipsOption...)} and friends instead.")
.build()
val newFromBytesNoOptionsMethod = MethodSpec.methodBuilder("newFromBytes")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(arenaType, "arena")
.addParameter(ArrayTypeName.of(TypeName.BYTE), "bytes")
.addParameter(vipsOptionVarargType, "options")
.returns(vimageType)
.varargs(true)
.addException(vipsErrorType)
.addStatement("var source = \$T.newFromBytes(arena, bytes)", vsourceType)
.addStatement("return newFromSource(arena, source, options)", vipsOptionSourceType)
.addJavadoc("See {@link VImage#newFromBytes(Arena, byte[], String, VipsOption...)}")
.build()
val writeToFileMethod = MethodSpec.methodBuilder("writeToFile")
.addModifiers(Modifier.PUBLIC)
.addParameter(stringType, "path")
Expand Down Expand Up @@ -617,6 +652,9 @@ object GenerateVClasses {
alphaMethod,
newFromFileMethod,
newFromSourceMethod,
newFromSourceNoOptionsMethod,
newFromBytesMethod,
newFromBytesNoOptionsMethod,
writeToFileMethod,
writeToImageMethod,
writeToTargetMethod,
Expand Down
7 changes: 7 additions & 0 deletions sample/src/main/kotlin/vipsffm/SampleHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ object SampleHelper {
)
}

val bytes = Files.readAllBytes(path)
if (bytes.all { it == 0.toByte() }) {
return Result.failure(
RuntimeException("file all zeroes")
)
}

return Result.success(Unit)
}
}
4 changes: 3 additions & 1 deletion sample/src/main/kotlin/vipsffm/SampleRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import vipsffm.sample.VImageCachingSample
import vipsffm.sample.VImageChainSample
import vipsffm.sample.VImageCopyWriteSample
import vipsffm.sample.VImageCreateThumbnailSample
import vipsffm.sample.VImageFromBytesSample
import vipsffm.sample.VImageJoinSample
import vipsffm.sample.VOptionHyphenSample
import vipsffm.sample.VSourceTargetSample
Expand Down Expand Up @@ -36,7 +37,8 @@ object SampleRunner {
VImageArrayJoinSample,
VBlobByteBufferSample,
VTargetToFileSample,
VImageJoinSample
VImageJoinSample,
VImageFromBytesSample
)
val sampleParentRunPath = Paths.get("sample_run")
if (Files.exists(sampleParentRunPath)) {
Expand Down
4 changes: 2 additions & 2 deletions sample/src/main/kotlin/vipsffm/VTargetMemorySample.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ object VTargetToFileSample: RunnableSample {

override fun run(arena: Arena, workingDirectory: Path): Result<Unit> {
val source = VSource.newFromFile(arena, "sample/src/main/resources/sample_images/fox.jpg")
val sourceImage = VImage.newFromSource(arena, source, "")
val sourceImage = VImage.newFromSource(arena, source)

val target = VTarget.newToMemory(arena)
sourceImage.writeToTarget(target, ".jpg")

val blob = target.blob
val outputSource = VSource.newFromBlob(arena, blob)
val outputPath = workingDirectory.resolve("fox_copy.jpg")
VImage.newFromSource(arena, outputSource, "")
VImage.newFromSource(arena, outputSource)
.writeToFile(outputPath.absolutePathString())

return SampleHelper.validate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object VBlobByteBufferSample: RunnableSample {
arena,
"sample/src/main/resources/sample_images/rabbit.jpg"
)
.thumbnailImage(400)
.thumbnailImage(400)

val blob = image.jpegsaveBuffer()
val bytes = blob.asClonedByteBuffer()
Expand Down
29 changes: 29 additions & 0 deletions sample/src/main/kotlin/vipsffm/sample/VImageFromBytesSample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package vipsffm.sample

import app.photofox.vipsffm.VImage
import app.photofox.vipsffm.VSource
import vipsffm.RunnableSample
import vipsffm.SampleHelper
import java.lang.foreign.Arena
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString

object VImageFromBytesSample: RunnableSample {

// note that this is generally inefficient, use a "VImage.newFrom" method if available
override fun run(arena: Arena, workingDirectory: Path): Result<Unit> {
val path = Path.of("sample/src/main/resources/sample_images/rabbit.jpg")
val bytes = Files.readAllBytes(path)
val image = VImage.newFromBytes(arena, bytes)
.thumbnailImage(400)

val outputPath = workingDirectory.resolve("rabbit_copy.jpg")
image.writeToFile(outputPath.absolutePathString())

return SampleHelper.validate(
outputPath,
expectedSizeBoundsKb = 20L..100L
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object VSourceTargetSample: RunnableSample {

override fun run(arena: Arena, workingDirectory: Path): Result<Unit> {
val source = VSource.newFromFile(arena, "sample/src/main/resources/sample_images/fox.jpg")
val sourceImage = VImage.newFromSource(arena, source, "")
val sourceImage = VImage.newFromSource(arena, source)

val sourceWidth = sourceImage.width
val sourceHeight = sourceImage.height
Expand Down

0 comments on commit 20fbb2c

Please sign in to comment.