Ve y hazlo estrellado

14

En este concurso, debe escribir un programa que acepte una imagen de píxeles en blanco y negro e intente alterarla, de modo que la forma blanca forme un dominio estelar , con el menor número de cambios posible.

Los cambios permitidos son convertir píxeles blancos en negros y convertir píxeles negros en blancos.

La salida debe consistir nuevamente en la misma imagen pero esta vez con todos los cambios y con / el centro marcado. Los píxeles que cambiaron de blanco a negro deben mostrarse en azul, los que cambiaron de negro a blanco deben mostrarse en amarillo, y al menos un píxel central debe mostrarse en rojo. (Los colores exactos dependen de usted). El programa debe superar la imagen especificada, así como la cantidad total de cambios realizados.

Definiciones

Dominio estrella

El conjunto de píxeles blancos de la imagen representa un dominio estelar si (y solo si) hay (al menos) un píxel central . El píxel central es uno de los píxeles blancos que se puede conectar mediante una línea recta a todos los demás píxeles blancos, de modo que la línea solo atraviesa píxeles blancos. (Por lo tanto, el píxel central no es necesariamente único).

Línea recta entre dos píxeles

Dados dos píxeles (inicio y final, ambos rojos en la ilustración a continuación), la línea recta entre los dos píxeles consiste en todos los píxeles, que tocan la línea (matemática, amarilla en la ilustración a continuación) que conduce desde el centro de la primera píxel al centro del último píxel. Un píxel no toca la línea si solo lo toca por una esquina, por lo que para que un píxel pertenezca a la línea de píxeles, la línea (matemática, amarilla) tiene que cruzar el píxel en cuestión con una longitud distinta de cero. (Si solo toca el punto de la esquina, esto se considera como longitud cero). Considere los siguientes ejemplos:

píxeles

Ejemplo

La primera imagen debe representar un ejemplo de 'entrada' de caso de prueba y las otras dos imágenes representan dos posibles salidas válidas para el ejemplo dado:

ejemplo de caso de prueba primer ejemplo de solución segunda solución de ejemplo

Las áreas amarillas (anteriormente negras) también cuentan para el dominio 'blanco', mientras que las áreas azules (anteriormente blancas) cuentan para la parte 'negra' fuera del dominio, y el punto rojo representa cada vez un píxel central posible.

Casos de prueba

Los siguientes casos de prueba son png con un tamaño de 256 x 256 píxeles.

caso de prueba 1 caso de prueba 2 caso de prueba 3 caso de prueba 4 caso de prueba 5 caso de prueba 6

Puntuación

Ejecute su programa con los siguientes casos de prueba e incluya el resultado (imagen / número de cambios) en su respuesta. Haré una tabla de clasificación para cada caso de prueba. Su puntaje será la suma de cada clasificación en la tabla de clasificación: cuanto menor sea el puntaje, mejor. Se aplican lagunas estándar. No está permitido hacer que el programa reconozca esos casos de prueba y ejecute un caso especial para ellos. (No está permitido calcular previamente y guardar los píxeles centrales óptimos para cada uno de esos casos de prueba). El programa debería funcionar para cualquier imagen.

Tabla de clasificación

Name        | Score | 1     - rk | 2     - rk | 3     - rk | 4     - rk | 5     - rk | 5     - rk | Total Changes
------------+-------+------------+------------+------------+------------+------------+------------+--------------
Maltysen    |    11 | 28688 -  2 | 24208 -  2 | 24248 -  1 |  7103 -  2 | 11097 -  2 | 13019 -  2 | 108363
TheBestOne  |     7 | 0     -  1 | 13698 -  1 | 24269 -  2 |   103 -  1 |  5344 -  1 |  4456 -  1 |  47867  
falla
fuente
2
Sería útil explicar la Fig. 1. ¿Por qué está conectando píxeles rojos, por ejemplo?
DavidC
44
No estoy muy seguro de lo que quieres decir. ¿Puede dar un antes y un después de uno de sus casos de prueba?
¿Qué tan cerca debe estar una línea de una esquina de píxeles para que se considere que pasa?
thenumberone
Agregué algunos ejemplos e intenté aclarar el texto, ¡espero que esté claro ahora!
flawr
¿Alguien más tiene la intención de intentar este desafío? Estoy un poco confundido, ya que algunas personas votaron a favor de este desafío, pero hasta ahora solo tenemos una respuesta (no muy seria). Alguna critica?
falla

Respuestas:

4

Java 8, 47.867 cambios en total.

Utiliza el promedio de la imagen como punto central. Luego dibuja todos los rayos posibles hacia el centro y le da el mejor radio para colorear. Luego colorea todos los puntos no válidos en negro.

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MakeItStarry {

    private static final int RGB_RED = Color.RED.getRGB();
    static int[][] originalImage;

    static final int WHITE = 0;
    static final int BLACK = 1;
    static final int RGB_WHITE = Color.WHITE.getRGB();
    static final int RGB_BLACK = Color.BLACK.getRGB();
    static final int RGB_BLUE = Color.BLUE.getRGB();
    static final int RGB_YELLOW = Color.YELLOW.getRGB();

    public static void main(String[] args) throws Exception{
        originalImage = convert(ImageIO.read(new File(args[0])));
        Point center = findCenter(originalImage);
        int[][] nextImage = starry(originalImage, center);
        BufferedImage result = difference(originalImage, nextImage);
        result.setRGB(center.x, center.y, RGB_RED);
        String fileType;
        String fileName;
        if (args[1].split("\\.").length > 1){
            fileType = args[1].split("\\.")[1];
            fileName = args[1];
        } else {
            fileType = "PNG";
            fileName = args[1] + ".PNG";
        }
        ImageIO.write(result, fileType, new File(fileName));
        System.out.println(cost);
    }

    static int cost;

    private static BufferedImage difference(int[][] image1, int[][] image2) {
        cost = 0;
        int height = image1[0].length;
        int width = image1.length;
        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++){
            for (int y = 0; y < width; y++){
                if (image1[x][y] == image2[x][y]){
                    if (image1[x][y] == WHITE){
                        result.setRGB(x, y, RGB_WHITE);
                    } else {
                        result.setRGB(x, y, RGB_BLACK);
                    }
                } else {
                    cost++;
                    if (image1[x][y] == WHITE){
                        result.setRGB(x, y, RGB_BLUE);
                    } else {
                        result.setRGB(x, y, RGB_YELLOW);
                    }
                }
            }
        }
        return result;
    }

    private static int[][] starry(int[][] image, Point center) {
        int width = image.length;
        int height = image[0].length;
        int[][] result = new int[width][height];
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                result[x][y] = BLACK;
            }
        }
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++) {
                Point endPoint = new Point(x, y, image);
                List<Point> line = Point.lineTo(center, endPoint, image);
                List<Point> newLine = starRay(line);
                newLine.stream().filter(point -> result[point.x][point.y] == BLACK).forEach(point -> {
                    result[point.x][point.y] = point.color;
                });
            }
        }
        int distance = 0;
        while (distance < height || distance < width){//This removes pixels that can't see the center.
            for (int x = Math.max(center.x - distance,0); x < center.x + distance && x < width; x++){
                for (int y = Math.max(center.y - distance, 0); y < center.y + distance && y < height; y++){
                    Point point = new Point(x, y, result);
                    if (Point.distance(center, point) != distance){
                        continue;
                    }
                    if (point.color == WHITE){
                        List<Point> line = Point.lineTo(center, point, result);
                        for (Point p : line){
                            if (p.color == BLACK){
                                point.color = BLACK;
                                break;
                            }
                        }
                        result[point.x][point.y] = point.color;
                    }
                }
            }//All white pixels can technically see the center but only if looking from the edge.
            distance++;
        }
        return result;
    }

    private static List<Point> starRay(List<Point> line) {
        int numOfWhites = 0;
        int farthestGoodPoint = 0;
        int blackCost = 0;
        int whiteCost = 0;
        for (int i = 0; i < line.size(); i++){
            if (line.get(i).color == WHITE){
                numOfWhites++;
                whiteCost++;
                if (numOfWhites + whiteCost > blackCost){
                    blackCost = 0;
                    whiteCost = 0;
                    farthestGoodPoint = i;
                }
            } else {
                blackCost++;
                numOfWhites = 0;
            }
        }
        List<Point> result = new ArrayList<>();
        for (int i = 0; i < line.size(); i++){
            Point p = line.get(i);
            if (i <= farthestGoodPoint){
                result.add(new Point(p.x, p.y, WHITE));
            } else {
                result.add(new Point(p.x, p.y, BLACK));
            }
        }
        return result;
    }

    private static Point findCenter(int[][] image) {
        double totalx = 0;
        double totaly = 0;
        int counter = 0;
        int width = image.length;
        int height = image[0].length;
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                if (image[x][y] == WHITE){
                    totalx += x;
                    totaly += y;
                    counter++;
                }
            }
        }
        return new Point((int)(totalx/counter), (int)(totaly/counter), image);
    }

    private static int[][] convert(BufferedImage image) {
        int width = image.getWidth();
        int height  = image.getHeight();
        int[][] result = new int[width][height];
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                if (image.getRGB(x, y) == RGB_WHITE){
                    result[x][y] = WHITE;
                } else {
                    result[x][y] = BLACK;
                }
            }
        }
        return result;
    }


    private static class Point {

        public int color;
        public int y;
        public int x;

        public Point(int x, int y, int[][] image) {
            this.x = x;
            this.y = y;
            this.color = image[x][y];
        }

        public Point(int x, int y, int color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public static List<Point> lineTo(Point point1, Point point2, int[][] image) {
            List<Point> result = new ArrayList<>();
            boolean reversed = false;
            if (point1.x > point2.x){
                Point buffer = point1;
                point1 = point2;
                point2 = buffer;
                reversed = !reversed;
            }
            int rise = point1.y - point2.y;
            int run = point1.x - point2.x;
            if (run == 0){
                if (point1.y > point2.y){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                int x = point1.x;
                for (int y = point1.y; y <= point2.y; y++){
                    result.add(new Point(x, y, image));
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
            if (rise == 0){
                if (point1.x > point2.x){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                int y = point1.y;
                for (int x = point1.x; x <= point2.x; x++){
                    result.add(new Point(x, y, image));
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
            int gcd = gcd(rise, run);
            rise /= gcd;
            run /= gcd;
            double slope = (rise + 0.0) / run;
            if (Math.abs(rise) >= Math.abs(run)){
                if (point1.y > point2.y){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                double x = point1.x;
                for (double y = point1.y + .5; y <= point2.y; y++){
                    int px = (int) Math.round(x);
                    if (Math.abs(Math.abs(px - x) - .5) < Math.abs(1.0 / (rise * 4))){
                        x += 1/slope;
                        continue;
                    }
                    result.add(new Point(px, (int) Math.round(y - .5), image));
                    result.add(new Point(px, (int) Math.round(y + .5), image));
                    x += 1/slope;
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            } else {
                if (point1.x > point2.x){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                double y = point1.y;
                for (double x = point1.x + .5; x <= point2.x; x++){
                    int py = (int) Math.round(y);
                    if (Math.abs(Math.abs(py - y) - .5) < Math.abs(1.0 / (run * 4))) {
                        y += slope;
                        continue;
                    }
                    result.add(new Point((int) Math.round(x - .5), py, image));
                    result.add(new Point((int) Math.round(x + .5), py, image));
                    y += slope;
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
        }

        private static List<Point> reversed(List<Point> points) {
            List<Point> result = new ArrayList<>();
            for (int i = points.size() - 1; i >= 0; i--){
                result.add(points.get(i));
            }
            return result;
        }

        private static int gcd(int num1, int num2) {
            if (num1 < 0 && num2 < 0){
                return -gcd(-num1, -num2);
            }
            if (num1 < 0){
                return gcd(-num1, num2);
            }
            if (num2 < 0){
                return gcd(num1, -num2);
            }
            if (num2 > num1){
                return gcd(num2, num1);
            }
            if (num2 == 0){
                return num1;
            }
            return gcd(num2, num1 % num2);
        }

        @Override
        public String toString(){
            return x + " " + y;
        }

        public static int distance(Point point1, Point point2) {
            return Math.abs(point1.x - point2.x) + Math.abs(point1.y - point2.y);
        }
    }
}

Resultados

Imagen 1 - 0 cambios, Imagen 2 - 13,698 cambios

12

Imagen 3 - 24,269 cambios, Imagen 4 - 103 cambios

34 4

Imagen 5 - 5,344 cambios, Imagen 6 - 4,456 cambios

5 56 6

Sin píxeles inválidos eliminados, 42,782 cambios en total

Los píxeles verdes son la primera capa de píxeles no válidos.

Imagen 1 - 0 cambios, Imagen 2- 9,889 cambios

12

Imagen 3 - 24,268 cambios, Imagen 4 - 103 cambios

34 4

Imagen 5 - 4,471 cambios, Imagen 6- 4,050 cambios

5 56 6

Todos los píxeles blancos en todas las imágenes pueden tener una línea dibujada desde el píxel central si la línea no tiene que originarse / terminar en los centros sino en cualquier parte del píxel.

args[0] contiene el nombre del archivo de entrada.

args[1] contiene el nombre del archivo de salida.

Imprime al stdoutnúmero de cambios.

El numero uno
fuente
¡Se ve muy bien! ¿Puedes explicar qué quieres decir con "píxeles no válidos"? No entendí bien eso. También en la imagen 2 en la parte inferior derecha, no pude entender por qué su programa se 'cava' en la pared negra, pero aún así vuelve a colorear los puntos blancos en negro, pero creo que esto tiene que ver con los 'píxeles no válidos' ¿no?
falla
Los pocos píxeles inválidos causan un efecto en cascada que hace que muchos más inválidos. Modificaré las últimas imágenes para mostrar la primera capa de píxeles no válidos en verde.
TheNumberOne
3

Python - PIL - 216,228 108,363 cambios en total

Whoo! ¡Córtalo a la mitad gracias a @AJMansfield! Este algoritmo omite todas las preocupaciones con el cálculo de líneas y la optimización y lo que no. Simplemente cambia todos los blancos a negro excepto uno. Si no hay blancos, uno se vuelve negro y blanco. Comprueba si hay más blancos o negros y cambia cada uno del otro tipo a excepción de uno. Si no hay negro, hace (0, 0) el centro.

import Image
from itertools import product

img = Image.open(raw_input())
img = img.convert("RGB")

pixdata = img.load()
changed=0

m=False
count=0
for x, y in product(xrange(img.size[1]), xrange(img.size[0])):
    if pixdata[x, y]==(0, 0, 0):
        count+=1

colors=[(0, 0, 0), (255, 255, 0)] if img.size[0]*img.size[1]-count>count else [(255, 255, 255), (0, 0, 255)]
m=False
for x, y in product(xrange(img.size[1]), xrange(img.size[0])):
    if pixdata[x, y] == colors[0]:
        if m:
            pixdata[x, y] = colors[1]
        else:
            pixdata[x, y] = (255, 0, 0)
            m=True
        changed+=1

if not m:
    pixdata[0, 0]==(255, 0, 0)
    changed+=1
if colors[0]==(255, 255, 255):
    changed-=1

print changed
img.save("out.png", "PNG")

Resultados

Imagen 1 - 28688 cambios, Imagen 2 - 24208 cambios

Imagen 3 - 24248 cambios, Imagen 4 - 7103 cambios

Imagen 5 - 11097 cambios, Imagen 6 - 13019 cambios

Toma el nombre del archivo de raw_input y escribe en out.png e imprime el número de cambios.

Maltysen
fuente
Tenga en cuenta que los píxeles que se cambiaron de negro a blanco deberían ser amarillos en su salida. Los que se cambiaron de blanco a negro deberían ser azules, y el centro (en su caso, su único píxel 'blanco' debería ser rojo en su salida. Aparte de eso, gracias por participar =) PD: Siempre debería ser posible crear un dominio estrella, incluso cuando tenga una imagen negra completa como entrada, puede cambiar un píxel a blanco (rojo).
flawr
Puede ser imposible si no hay píxeles blancos o negros (es decir, a todo color). En cualquier caso, estoy haciendo los otros cambios.
Maltysen
Oh. Imagen en blanco y negro. Culpa mía.
Maltysen
Creo que podría ser más eficiente hacer la estrategia opuesta y cambiar todos los píxeles negros a blancos. ¿Intentaste eso?
AJMansfield
@AJMansfield Creo que esto solo sería más eficiente para el caso de prueba dado, por lo que quizás esto ya podría considerarse como un condicionamiento del algoritmo para los casos de prueba dados.
flawr