Quote:
Originally Posted by mreff555
Not exactly.
|
The basic mapping is
Code:
angle: angle of target image left edge in source image, radians
center_x,
center_y: centerpoint in source image
top_radius: radius in source image that corresponds to target image top edge
bottom_radius: radius in source image that corresponds to target image bottom edge
a = angle + 2 * Pi * target_x / target_width
r = function( target_y / target_height ) * (bottom_radius - top_radius) + top_radius
source_x = center_x + r * cos(angle + 2 * Pi * target_x / target_width)
source_y = center_y + r * sin(angle + 2 * Pi * target_x / target_width)
where
function depends on the projection. In your case, simple
r = target_y / target_height * (bottom_radius - top_radius) + top_radiusshould work well, although circles will not stay exactly circles. You can use a different function to compensate, but then details will be relatively larger the closer they are to the center of the ring.
Here is a simple C program I cobbled together. It consumes a PGM image, and produces one:
Code:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <errno.h>
#define PI 3.1415926535897932384626434
#define PI_PER_180 0.0174532925199432957692369
struct pgm {
int width;
int height;
size_t stride;
uint8_t pixel[];
};
/* Helper function: Skips blanks and PNM comments.
*/
static inline int pnm_next(FILE *const in, int c)
{
while (1) {
/* Skip blanks. */
while (c == '\t' || c == '\n' || c == '\v' ||
c == '\f' || c == '\r' || c == ' ')
c = getc(in);
/* Not a comment? */
if (c != '#')
return c;
/* Skip comment line. */
while (c != EOF && c != '\n' && c != '\r')
c = getc(in);
}
}
/* Helper function: Parse nonnegative integer.
*/
static inline int pnm_int(FILE *const in, int c, int *const result)
{
int val = 0;
/* Prime result with invalid value. */
if (result)
*result = -1;
/* Parse decimal digits, checking for overflow. */
while (c >= '0' && c <= '9') {
const int old = val;
val = 10 * val + c - '0';
if (val < old)
return EOF;
c = getc(in);
}
/* Save result. */
if (result)
*result = val;
return c;
}
/* Allocate a new, uninitialized pgm image.
* Pixel data is pgm->pixel[x + y*PGM->stride].
*/
struct pgm *pgm_new(const int width, const int height)
{
struct pgm *image;
if (width < 1 || height < 1) {
errno = EINVAL;
return NULL;
}
image = malloc(sizeof (struct pgm) + (size_t)width * (size_t)height * (size_t)sizeof (uint8_t));
if (!image) {
errno = ENOMEM;
return NULL;
}
image->width = width;
image->height = height;
image->stride = (size_t)width;
return image;
}
/* Save image as a 8-bit PGM file.
*/
int pgm_save(const struct pgm *const image, FILE *const out)
{
int y;
if (!image || !image->stride || !out)
return errno = EINVAL;
if (fprintf(out, "P5\n%d %d\n255\n", image->width, image->height) < 0)
return errno = EIO;
for (y = 0; y < image->height; y++)
if (fwrite(image->pixel + (size_t)y * image->stride, (size_t)image->width, 1, out) != 1)
return errno = EIO;
if (fflush(out))
return errno = EIO;
return 0;
}
/* Load 8-bit PGM file as an image.
* Note: Input samples are rescaled to 0..255.
*/
struct pgm *pgm_load(FILE *const in)
{
struct pgm *image;
size_t size;
int width, height, max, c, x, y;
if (!in) {
errno = EINVAL;
return NULL;
}
/* Only binary pgm files are supported. */
if (getc(in) != 'P') {
errno = EEXIST;
return NULL;
}
if (getc(in) != '5') {
errno = EEXIST;
return NULL;
}
/* Skip whitespace, parse width. */
c = pnm_next(in, getc(in));
c = pnm_int(in, c, &width);
if (c == EOF || width < 1) {
errno = EEXIST;
return NULL;
}
/* Skip whitespace, parse height. */
c = pnm_next(in, c);
c = pnm_int(in, c, &height);
if (c == EOF || height < 1) {
errno = EEXIST;
return NULL;
}
/* Skip whitespace, parse maximum component value. */
c = pnm_next(in, c);
c = pnm_int(in, c, &max);
if (c == EOF || max < 1 || max > 255) {
errno = EEXIST;
return NULL;
}
/* Calculate size needed for the graymap data. Check for overflow. */
size = (size_t)width * (size_t)height;
if (size / (size_t)width != (size_t)height) {
errno = ENOMEM;
return NULL;
}
if (size > (size_t)(size + sizeof (struct pgm))) {
errno = ENOMEM;
return NULL;
}
/* Allocate image, and fill in the fields. */
image = malloc(sizeof (struct pgm) + size);
if (!image) {
errno = ENOMEM;
return NULL;
}
image->width = width;
image->height = height;
image->stride = (size_t)width;
/* Read image data. */
for (y = 0; y < height; y++)
if (fread(image->pixel + (size_t)y * image->stride, (size_t)width, 1, in) != 1) {
free(image);
errno = EIO;
return NULL;
}
/* Rescale 0..max to 0..255 */
if (max > 0 && max < 255)
for (y = 0; y < height; y++) {
uint8_t *const row = image->pixel + (size_t)y * image->stride;
for (x = 0; x < width; x++)
row[x] = (255 * (int)(row[x])) / max;
}
/* Success. */
return image;
}
/* Map a ring from source image into the target image.
* Center of the ring is at (centerx, centery),
* top (y=0) at radius rtop, bottom at radius rbottom,
* with left edge (x=0) at angle (angle).
* (For angle, 360 is full circle, 0 and 360 towards +x,
* 90 towards +y.)
* Note: This uses nearest-neighbor; no antialiasing.
*/
void pgm_ring(struct pgm *const target,
const struct pgm *const source,
const float centerx, const float centery,
const float angle,
const float rtop, const float rbottom,
const uint8_t outside)
{
const float a0 = angle * PI_PER_180;
const float da = PI / (float)target->width;
const float dr = (rbottom - rtop) / (float)(target->height - 1);
int x, y;
for (y = 0; y < target->height; y++) {
const float r = rtop + dr * (float)y;
for (x = 0; x < target->width; x++) {
const float a = a0 + da * (float)x;
int xc = (int)(0.5f + centerx + r * cos(a));
int yc = (int)(0.5f + centery + r * sin(a));
if (xc >= 0 && xc < source->width &&
yc >= 0 && yc < source->height)
target->pixel[y * target->stride + x] = source->pixel[yc * source->stride + xc];
else
target->pixel[y * target->stride + x] = outside;
}
}
}
int main(void)
{
struct pgm *in;
struct pgm *out;
int radius;
/* Read source image from standard input. */
in = pgm_load(stdin);
if (!in) {
fprintf(stderr, "Standard input is not a valid PGM image.\n");
return 1;
}
fprintf(stderr, "Read %dx%d PGM image.\n", in->width, in->height);
fflush(stderr);
/* Radius depends on the shorter edge length. */
if (in->width < in->height)
radius = in->width / 2;
else
radius = in->height / 2;
/* Create an image about 2*radius in width, radius/4 in height. */
out = pgm_new((int)(2.0943951f * radius), (int)(0.25f * radius));
if (!out) {
fprintf(stderr, "Failed to create a new PGM image.\n");
return 1;
}
/* Map the source image (radius .. radius/2). */
pgm_ring(out, in,
0.5f * in->width, 0.5f * in->height,
90.0f,
1.00f * radius, 0.667f * radius,
(uint8_t)255);
/* Save the result to standard output. */
if (pgm_save(out, stdout)) {
fprintf(stderr, "Error writing PGM image to standard output.\n");
return 1;
}
fprintf(stderr, "Successfully saved %dx%d PGM image to standard output.\n", out->width, out->height);
free(out);
free(in);
return 0;
}
If you save it as
pgmring.c, you can compile it using
Code:
gcc pgmring.c -Wall -O3 -o pgmring
It takes in a PGM image, and outputs the result as another, so to run with your TIFF images, install the
netpbm package (for PBM tools), and run
Code:
tifftopnm out.tif | ./pgmring | pnmtotiff > result.tif
Attached is the resulting image converted to PNG instead (for smaller size).
The key function is very simple:
Code:
struct pgm {
int width;
int height;
size_t stride;
uint8_t pixel[];
};
void pgm_ring(struct pgm *const target,
const struct pgm *const source,
const float centerx, const float centery,
const float angle,
const float rtop, const float rbottom,
const uint8_t outside)
{
const float a0 = angle * PI_PER_180;
const float da = PI / (float)target->width;
const float dr = (rbottom - rtop) / (float)(target->height - 1);
int x, y;
for (y = 0; y < target->height; y++) {
const float r = rtop + dr * (float)y;
for (x = 0; x < target->width; x++) {
const float a = a0 + da * (float)x;
int xc = (int)(0.5f + centerx + r * cos(a));
int yc = (int)(0.5f + centery + r * sin(a));
if (xc >= 0 && xc < source->width &&
yc >= 0 && yc < source->height)
target->pixel[y * target->stride + x] = source->pixel[yc * source->stride + xc];
else
target->pixel[y * target->stride + x] = outside;
}
}
}
Here,
r is a linear function of target image y coordinate; from
rtop to
rbottom. If you need specific features, like keeping the M5 dots circular, this is the function you'll need to modify. (I think r(y) = Y1 + Y2 / (y + Y3) should yield circular dots, detail size increasing as you go closer to center, but I'm too lazy to calculate how the constants Y1, Y2, Y3 depend on the target image height and top and bottom radii.)
Source x and y coordinates (
xc and
yc) are calculated in polar coordinates around the specified centerpoint, with angle increasing from +x to +y axes, and left edge of target image being at angle
angle .
The function does not do any kind of interpolation; it uses the nearest neighbor algorithm. You can speed it up by precalculating the sin/cos table, and perhaps the r table as well; you don't even really need floating-point support.
For interpolation, the simplest option is to estimate the size and shape of the region in the source image that affects each target pixel -- remember, pixels are mathematically
samples, not rectangular dots -- then sample that region a few times randomly to obtain the sample in the target image. I recommend using Xorshift to generate the random numbers; it works very well for this, and is quite fast. Although mathematically one should vary the target image coordinates using something like a normal distribution, random sampling in the tetragonal area defined by the target image pixel "corners" works quite well in practice. You can extend the areas a bit to soften the image; you'll lose any crisp edges anyway, so a few percent overlap will typically enhance the observed image quality, although it does blur it very slightly.