Skip to content
Draft
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 build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ subprojects {
runs {
client {
property 'item_asset_export.render.namespaces', 'exposure'
vmArgs '-XX:+AllowEnhancedClassRedefinition', '-XX:HotswapAgent=fatjar', '-Dsodium.checks.issue2561=false'
vmArgs '-XX:+.AllowEnhancedClassRedefinition', '-XX:HotswapAgent=fatjar', '-Dsodium.checks.issue2561=false'
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions common/src/main/java/io/github/mortuusars/exposure/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ public static class Common {
public static class Client {
public static final ForgeConfigSpec SPEC;

// Camera
public static final ForgeConfigSpec.BooleanValue USE_FRAME_STACKING;

// UI
public static final ForgeConfigSpec.BooleanValue RECIPE_TOOLTIPS_WITHOUT_JEI;
public static final ForgeConfigSpec.BooleanValue CAMERA_SHOW_TOOLTIP_DETAILS;
Expand Down Expand Up @@ -330,6 +333,17 @@ public static class Client {
static {
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();

{
builder.push("camera");

USE_FRAME_STACKING = builder
.comment("Use more realistic frame stacking, instead of just brightening an image." +
"May cause a client-side freeze. Default: false")
.define("use_frame_stacking", false);

builder.pop();
}

{
builder.push("ui");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ static CaptureAction forceRegularOrSelfieCamera(@Nullable CameraHolder holder) {
}

static CaptureAction hideGui() {
return new HideGuiAction();
// Looks very weird with my code, so I have to remove it
return EMPTY;
//return new HideGuiAction();
}

static CaptureAction disablePostEffect() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.github.mortuusars.exposure.client.capture.task;

import com.mojang.blaze3d.platform.NativeImage;
import io.github.mortuusars.exposure.client.image.WrappedNativeImage;
import io.github.mortuusars.exposure.client.util.Minecrft;
import io.github.mortuusars.exposure.util.cycles.task.Result;
import io.github.mortuusars.exposure.util.cycles.task.Task;
import io.github.mortuusars.exposure.client.image.Image;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class StackedScreenshotCaptureTask extends Task<Result<Image>> {
@Nullable
private CompletableFuture<Result<Image>> future;

private final int framesToCapture;
private final float shutterBrightness;
private int framesCaptured = 0;
private final List<NativeImage> frames = new ArrayList<>();
private long lastGameTick = -1;

public StackedScreenshotCaptureTask(int durationTicks, float shutterBrightness) {
this.framesToCapture = Math.max(1, durationTicks);
this.shutterBrightness = shutterBrightness;
this.lastGameTick = getCurrentGameTick();
}

@Override
public @NotNull CompletableFuture<Result<Image>> execute() {
if (future == null) {
future = new CompletableFuture<>();
}
return future;
}

@Override
public void tick() {
if (future == null || future.isDone()) {
return;
}
if (getCurrentGameTick() == lastGameTick) return;
lastGameTick = getCurrentGameTick();

try {
NativeImage img = Screenshot.takeScreenshot(Minecraft.getInstance().getMainRenderTarget());
frames.add(img);
framesCaptured++;
} catch (Exception e) {
for (NativeImage frame : frames) {
frame.close();
}
future.completeExceptionally(e);
return;
}

if (framesCaptured >= framesToCapture && !future.isDone()) {
try {
NativeImage accumulated = accumulateFrames(frames, shutterBrightness);
future.complete(Result.success(new WrappedNativeImage(accumulated)));
} catch (Exception e) {
for (NativeImage frame : frames) {
frame.close();
}
future.completeExceptionally(e);
}
}
}

private NativeImage accumulateFrames(List<NativeImage> images, float exposureMultiplier) {
if (images.isEmpty()) throw new IllegalArgumentException("Cannot accumulate 0 frames");

int w = images.get(0).getWidth();
int h = images.get(0).getHeight();
NativeImage result = new NativeImage(NativeImage.Format.RGBA, w, h, false);

float perFrameWeight = exposureMultiplier / images.size();

for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
float a = 0, b = 0, g = 0, r = 0;

for (NativeImage frame : images) {
int pixel = frame.getPixelRGBA(x, y);
float frameA = ((pixel >> 24) & 0xFF) / 255f;
float frameB = ((pixel >> 16) & 0xFF) / 255f;
float frameG = ((pixel >> 8) & 0xFF) / 255f;
float frameR = (pixel & 0xFF) / 255f;

a += frameA;
b += frameB * perFrameWeight;
g += frameG * perFrameWeight;
r += frameR * perFrameWeight;
}

// Clamp to valid range [0, 1]
a = Math.min(1f, Math.max(0f, a / images.size()));
b = Math.min(1f, Math.max(0f, b));
g = Math.min(1f, Math.max(0f, g));
r = Math.min(1f, Math.max(0f, r));

int outA = (int) (a * 255);
int outB = (int) (b * 255);
int outG = (int) (g * 255);
int outR = (int) (r * 255);

result.setPixelRGBA(x, y, (outA << 24) | (outB << 16) | (outG << 8) | outR);
}
}

// Close all source frames to prevent memory leaks
for (NativeImage frame : images) {
frame.close();
}

return result;
}

private long getCurrentGameTick() {
return Minecrft.level().getGameTime();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import io.github.mortuusars.exposure.client.capture.action.CaptureAction;
import io.github.mortuusars.exposure.client.capture.palettizer.Palettizer;
import io.github.mortuusars.exposure.client.capture.saving.ExposureUploader;
import io.github.mortuusars.exposure.client.capture.task.StackedScreenshotCaptureTask;
import io.github.mortuusars.exposure.client.image.Image;
import io.github.mortuusars.exposure.client.util.Minecrft;
import io.github.mortuusars.exposure.util.cycles.task.Result;
import io.github.mortuusars.exposure.world.camera.capture.CaptureParameters;
import io.github.mortuusars.exposure.world.camera.capture.Projection;
import io.github.mortuusars.exposure.data.ColorPalette;
Expand Down Expand Up @@ -36,17 +39,26 @@ public Task<?> createTask(CaptureParameters params) {
}

@Nullable CameraHolder holder = entity instanceof CameraHolder cameraHolder ? cameraHolder : null;

Holder<ColorPalette> palette = getColorPalette(params);

Task<ExposureData> captureTask = Capture.of(Capture.screenshot(),
final boolean USE_FRAME_STACKING = Config.Client.USE_FRAME_STACKING.get();
Task<Result<Image>> screenshotTask = USE_FRAME_STACKING
? new StackedScreenshotCaptureTask(
params.getShutterSpeed().getDurationTicks(),
// I decrease it because else it looks much brighter than without stacking
params.getShutterSpeed().getBrightness() * 0.5f)
: Capture.screenshot();

Task<ExposureData> captureTask = Capture.of(screenshotTask,
CaptureAction.setCameraEntity(entity),
CaptureAction.forceRegularOrSelfieCamera(holder),
CaptureAction.optional(params.fov(), CaptureAction::setFov),
// Looks weird with my code
// See: HideGuiAction.java
CaptureAction.hideGui(),
CaptureAction.optional(!Config.Client.KEEP_POST_EFFECT.get(), CaptureAction::disablePostEffect),
CaptureAction.setFilter(params.filter()),
CaptureAction.modifyGamma(params.getShutterSpeed()),
CaptureAction.optional(!USE_FRAME_STACKING, CaptureAction.modifyGamma(params.getShutterSpeed())),
CaptureAction.optional(params.getFlash(), () -> CaptureAction.flash(entity)))
.handleErrorAndGetResult(printCasualErrorInChat())
.thenAsync(applyEffectsToImage(params))
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ create_version_range_reason=Exposure 1.9.12+ needs Create 6.0.7+ for sequenced f
create_version = 6.0.8-289
ponder_version = 1.0.91
flywheel_version = 1.0.5
registrate_version = MC1.20-1.3.3
registrate_version = MC1.20-1.3.3
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
37 changes: 22 additions & 15 deletions gradlew
100644 → 100755

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 14 additions & 12 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.