C
C#3w ago
BenMcLean

Upscaling and rotating texture

I've got a method for upscaling and rotating a texture and it essentially works already except that I'm trying to make it more efficient by making it so it only loops over the parts of the result that could potentially have pixels. (skip over known empty parts) In my visualization here, purple is startX and orange is endX. If ths problem was solved, purple would follow the bounding box's left edge and orange would follow the bounding box's right edge. This is really a geometry problem, not even C# specific. But every time I try to do it, I screw it up so that it cuts off part of the scaled and rotated result texture and Claude the LLM fails at this really hard. Minimum reproduction project is here: https://github.com/BenMcLean/UpscaleAndRotate
GitHub
GitHub - BenMcLean/UpscaleAndRotate
Contribute to BenMcLean/UpscaleAndRotate development by creating an account on GitHub.
No description
17 Replies
BenMcLean
BenMcLeanOP3w ago
Here's the code.
BenMcLean
BenMcLeanOP3w ago
Rather than just fixing the code, I'd appreciate if anyone could even just describe the correct approach to solving this. I mean, I can write code but I suck at math
ero
ero3w ago
i'm curious, do you have a small sample app where i can run this code to view the result? i can't promise i'll be able to help, but i'm interested in the technique nonetheless
BenMcLean
BenMcLeanOP3w ago
I guess I need to make that. The whole thing's open source but it's part of a huge library
Tijmen
Tijmen3w ago
//double[][] corners = [
//[-halfRotatedWidth + offsetX, -halfRotatedHeight + offsetY],//top-left
//[halfRotatedWidth + offsetX, -halfRotatedHeight + offsetY],//top-right
//[halfRotatedWidth + offsetX, halfRotatedHeight + offsetY],//bottom-right
//[-halfRotatedWidth + offsetX, halfRotatedHeight + offsetY],//bottom-left
//];
//double[][] corners = [
//[-halfRotatedWidth + offsetX, -halfRotatedHeight + offsetY],//top-left
//[halfRotatedWidth + offsetX, -halfRotatedHeight + offsetY],//top-right
//[halfRotatedWidth + offsetX, halfRotatedHeight + offsetY],//bottom-right
//[-halfRotatedWidth + offsetX, halfRotatedHeight + offsetY],//bottom-left
//];
Are these correct when you draw them out? Then its just a case of the min of that no, for the startX.
BenMcLean
BenMcLeanOP3w ago
i'm trying to get the correct startX for each scanline, which is only going to be the minimum corner on the one scanline that happens to intersect the corner. also one of the corners is always going to be on x=0 as per the method description i had started to get the corners in order to try to figure out how to get the intercepts of the lines between the corners which would be the real startX and endX but then again i have never actually drawn those out so i probably should
Tijmen
Tijmen3w ago
Ahh per scan line
BenMcLean
BenMcLeanOP3w ago
yeah. i think this should save a lot of CPU and RAM actually, especially on large images and even more when I need to run the same algorithm on Z-slices of a voxel model to make sprite stacks
Tijmen
Tijmen3w ago
Different shape but same idea i think?
BenMcLean
BenMcLeanOP3w ago
I created a minimum reproduction project. The visualization is created by running the only unit test. https://github.com/BenMcLean/UpscaleAndRotate
GitHub
GitHub - BenMcLean/UpscaleAndRotate
Contribute to BenMcLean/UpscaleAndRotate development by creating an account on GitHub.
BenMcLean
BenMcLeanOP3w ago
and the challenge is to implement the only TODO I got my unit test lookin pretty slick.
using SixLabors.ImageSharp;

namespace UpscaleAndRotate.Test;

public class UpscaleAndRotateTest
{
[Theory]
[InlineData(160, 319, 1, 1, 64, 10, "rotated.gif")]
public void RotateTest(ushort width, ushort height, byte scaleX, byte scaleY, ushort numFrames, int frameDelay, string path)
{
byte[] texture = new byte[width * height << 2].DrawBoundingBox(width);
byte[][] frames = ImageMaker.SameSize(
frames: [.. Enumerable.Range(0, numFrames)
.Parallelize(i => (texture
.Rotate(
rotatedWidth: out ushort rotatedWidth,
rotatedHeight: out ushort _,
radians: Math.Tau * ((double)i / numFrames),
scaleX: scaleX,
scaleY: scaleY,
width: width), rotatedWidth))],
width: out ushort newWidth,
height: out _);
ImageMaker.AnimatedGif(
width: newWidth,
frameDelay: frameDelay,
frames: frames)
.SaveAsGif(path);
}
}
using SixLabors.ImageSharp;

namespace UpscaleAndRotate.Test;

public class UpscaleAndRotateTest
{
[Theory]
[InlineData(160, 319, 1, 1, 64, 10, "rotated.gif")]
public void RotateTest(ushort width, ushort height, byte scaleX, byte scaleY, ushort numFrames, int frameDelay, string path)
{
byte[] texture = new byte[width * height << 2].DrawBoundingBox(width);
byte[][] frames = ImageMaker.SameSize(
frames: [.. Enumerable.Range(0, numFrames)
.Parallelize(i => (texture
.Rotate(
rotatedWidth: out ushort rotatedWidth,
rotatedHeight: out ushort _,
radians: Math.Tau * ((double)i / numFrames),
scaleX: scaleX,
scaleY: scaleY,
width: width), rotatedWidth))],
width: out ushort newWidth,
height: out _);
ImageMaker.AnimatedGif(
width: newWidth,
frameDelay: frameDelay,
frames: frames)
.SaveAsGif(path);
}
}
you run the unit test and it writes the animation to "rotated.gif' inside the test project's bin folder ... or wherever else you decide to send it to on your computer by modifying that string. you just open the gif to see the result My Parallelize is like Select except it'll run multi-threaded if the runtime allows multi-threading while acting just like regular Select if it doesn't allow multi-threading.
/// <summary>
/// Parallelizes the execution of a Select query while preserving the order of the source sequence.
/// </summary>
public static List<TResult> Parallelize<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) => [.. source
.Select((element, index) => (element, index))
.AsParallel()
.Select(sourceTuple => (result: selector(sourceTuple.element), sourceTuple.index))
.OrderBy(resultTuple => resultTuple.index)
.AsEnumerable()
.Select(resultTuple => resultTuple.result)];
/// <summary>
/// Parallelizes the execution of a Select query while preserving the order of the source sequence.
/// </summary>
public static List<TResult> Parallelize<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) => [.. source
.Select((element, index) => (element, index))
.AsParallel()
.Select(sourceTuple => (result: selector(sourceTuple.element), sourceTuple.index))
.OrderBy(resultTuple => resultTuple.index)
.AsEnumerable()
.Select(resultTuple => resultTuple.result)];
Yeah and that is a really good article so thanks. I'm bookmarking that because I had planned to implement Bresenham's lines, triangles, circles and ellipses at some point only I haven't got around to it yet. I don't actually NEED all of those per se because the point of my library is to render voxels but they'd be nice to have. anyway the problem of the moment is getting the purple and orange lines representing startX and endX to the correct positions on each scanline
BenMcLean
BenMcLeanOP3w ago
I tried feeding Claude the minimum reproduction project and asked it to implement the TODO and the result has your typical LLM glitchiness.
No description
BenMcLean
BenMcLeanOP3w ago
Attached is Claude's wrong solution.
BenMcLean
BenMcLeanOP3w ago
I built a test that could export five images for Claude to examine and I think it managed to solve it after just two iterations. I'm amazed.
No description
BenMcLean
BenMcLeanOP3w ago
Yeah I was able to clean up Claude's solution and it is beautiful now.
BenMcLean
BenMcLeanOP3w ago
wow check out my sprite stack
No description
Want results from more Discord servers?
Add your server