Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to make a VBlob from some bytes #104

Merged
merged 1 commit into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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