简体   繁体   中英

Dividing a rectangle into smaller, equal, integer rectangles/tiles of maximum size

I need to create screenshots with an extremely large resolution. Because the resolution is larger than my renderer supports, they need to be split up into smaller resolutions and stitched together later.

I have figured out the stitching/rendering part of rectangles, but I am struggling trying to find the most optimal rectangle resolution to use for each tile.

My renderer has a limit of 4096x4096.

For resolutions like 16384x16384 this would be easy to figure out: 4*4 rectangles of 4096x4096.

My input resolutions aren't always a power of two however, one such example could be 5005x5000, for which the optimal rectangle size would be 5*2 rectangles of 1001x2500.

What I'm looking for is a function that, when given a desired resolution and a maximum resolution, outputs a resolution within the maximum resolution that can be multiplied to reach the desired resolution.

Something like this:

public IntRect FindOptimalTileSize(IntRect desiredResolution, IntRect maximumTileSize)
{
    //some magic
    return new IntRect(magic.X, magic.Y);
}

The output requirements in a row:

  • Resolution x and y must be integer
  • Resolution x and y must be lower than given maximum x and y
  • Resolution x and y must be an integer division of desiredResolution
  • Resolution x and y should be as high as possible, as long as they satisfy the other rules
  • Aspect ratio doesn't need to be retained

I have tried searching the internet for a solution, but those all seem to be for a different, more common usecase.

It might not always be possible to find a rectangle that satisfies all my rules, in which case I'd like the function to return something distinguishable, like -1x-1

I'm no math expert, but this might get you a starting point. This code is a sketch. It is not production-ready. It is not of beta quality. It is only a sketch and you will have to do a lot of work to clean it up and make it usable.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1 {
    class Program {
        static void Main(string[] args) {

            int resultX = -1;
            int resultY = -1;
            int sourceX = 5005;
            int sourceY = 5000;
            int targetX = 4096;
            int targetY = 4096;

            if (sourceX <= targetX) { resultX = sourceX; }
            if (sourceY <= targetY) { resultY = sourceY; }
            if (IsValid(resultX, resultY)) {
                //  return the results
                Console.WriteLine($"x={resultX}, y={resultY}");
                return;
            }

            for (int index=targetX; 0 < index; index--) {
                double result = (double)sourceX / index;
                if (0 == result - (int)result) {
                    resultX = index;
                    break;
                }
            }

            for (int index = targetY; 0 < index; index--) {
                double result = (double)sourceY / index;
                if (0 == result - (int)result) {
                    resultY = index;
                    break;
                }
            }

            Console.WriteLine($"x={resultX}, y={resultY}");
            Console.ReadKey(true);
        }


        static bool IsValid(int x, int y) {
            return (-1 != x) && (-1 != y);
        }
    }
}

Your problem can be broken down into two equal problems: find the equal integer partition p of an integer n where p < m.

Using an extension method and some helpers, you can generate the Prime Factorization of n:

static IEnumerable<int> PotentialPrimes() { // fails once int.MaxValue exceeded
    yield return 2;
    yield return 3;
    var pp = 5;
    for (; ; ) {
        yield return pp;
        yield return pp + 2;
        pp += 6;
    }
}
public static IEnumerable<int> Primes() {
    return PotentialPrimes().Where(p => {
        var sqrt = (int)Math.Floor(Math.Sqrt(p));
        return !PotentialPrimes().TakeWhile(f => f <= sqrt)
                                 .Any(f => p % f == 0);
    });
}

public static IEnumerable<int> PrimeFactors(this int n) {
    var maxDivisor = (int)Math.Floor(Math.Sqrt(n));
    var testDivisors = Primes().TakeWhile(pp => pp < maxDivisor);

    foreach (var f in testDivisors)
        for (; n % f == 0; n /= f)
            yield return f;

    if (n != 1)
        yield return n;
}

Now, using the prime factorization, you can find the largest p less than m (per the comment, if n > m, then n is returned):

public static int LargestPartition(this int n, int maxPartitionSize) {
    var factors = n.PrimeFactors().Where(f => f <= maxPartitionSize);
    if (factors.Any()) {
        var flist = factors.OrderByDescending(f => f).ToList();
        var partition = flist.First();
        foreach (var f in flist.Skip(1)) {
            var tp = partition * f;
            if (tp <= maxPartitionSize)
                partition = tp;
        }

        return partition;
    }
    else
        return n;
}

Finally, just apply LargestPartition to each side of the rectangles:

public IntRect FindOptimalTileSize(IntRect desiredResolution, IntRect maximumTileSize) {
    return new IntRect(desiredResolution.X.LargestPartition(maximumTileSize.X),
                       desiredResolution.Y.LargestPartition(maximumTileSize.Y));
}

NOTE: I updated my PrimeFactors function to be faster for more generic cases in case someone comes across this. Where previously I couldn't compute from 2 to int.MaxValue in an hour, now it takes 36 seconds. The Math.Sqrt could be replaced if performance was still an issue, but I would think by that point other overhead would dominate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM