วาดภาพด้วยเส้นโค้งที่ปิดเพียงเส้นเดียว


74

แรงบันดาลใจจากvi.sualize.us

เป้าหมาย

อินพุตเป็นภาพสีเทาและเอาต์พุตเป็นภาพขาวดำ ภาพที่ส่งออกประกอบด้วยเส้นโค้งที่ปิดเพียงหนึ่ง (วน) ที่ไม่ได้รับอนุญาตให้ตัดกับตัวเองหรือสัมผัสตัวเอง ความกว้างของเส้นจะคงที่ตลอดทั้งภาพ ความท้าทายที่นี่คือการหาอัลกอริทึมสำหรับการทำเช่นนั้น เอาท์พุทเพียงเพื่อแสดงภาพอินพุต แต่ด้วยอิสระทางศิลปะใด ๆ ความละเอียดไม่สำคัญนัก แต่อัตราส่วนภาพควรอยู่ในระดับเดียวกัน

ตัวอย่าง

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ภาพทดสอบเพิ่มเติม

ทะเลสาบล็อคเนส มีดโกนท้องฟ้า Einstein ตาหมากรุก


2
คุณอาจต้องการที่จะนำบางข้อ จำกัด ในมติญาติ มิฉะนั้นเพียงแค่เพิ่มความละเอียดมากขึ้น (พูดถึงปัจจัย 32 หรือบางสิ่ง) แล้วแทนที่แต่ละพิกเซลด้วย 32x32 บล็อกของความเข้มเฉลี่ยที่เหมาะสม มันควรจะง่ายพอที่จะทำให้บล็อกทั้งหมดเชื่อมต่อกันและจัดเรียงมันในลักษณะที่ทุกอย่างเชื่อมต่อกับลูปเดียว
Martin Ender

1
หากเส้นไม่สามารถสัมผัสตัวเองได้ไม่มีพื้นที่มืดเฉดสีเข้มจะเป็นสีเทา 50%
edc65

1
@ มาร์ตินThe width of the line shall be constant throughout the whole image.แต่ก็ยังเป็นคำใบ้ที่มีประโยชน์
edc65

2
@ edc65 ใช่คงที่ แต่คุณยังสามารถทำให้มันกว้างกว่าหนึ่งพิกเซล (ต่อเนื่อง) ซึ่งในกรณีนี้คุณสามารถแยกบรรทัดสองส่วนด้วยหนึ่งพิกเซลจากนั้นพื้นที่นั้นจะเข้มกว่าค่าเฉลี่ย 50%
Martin Ender

2
@githubphagocyte ภาพส่วนใหญ่ควรเป็นแบบขาวดำ แต่ไม่สำคัญว่าจะมีเอฟเฟ็กต์การลบรอยหยักหรือไม่ และคุณควรพยายามหลีกเลี่ยงสถานการณ์ที่มีการสัมผัสพิกเซลในแนวทแยงมุม แต่อีกครั้งหากสิ่งนี้เกิดขึ้นเพียงไม่กี่ครั้งในภาพมันจะโอเคตราบใดที่คุณไม่ได้ใช้อย่างเป็นระบบ ขอบคุณสำหรับการป้อนข้อมูล @ edc65: ใช่ฉันรู้ว่าเป้าหมายคือผู้ดูยังสามารถระบุหนึ่งบรรทัดที่แตกต่างกันในภาพ (เมื่อซูมเข้า)
ข้อบกพร่อง

คำตอบ:


34

Java: สไตล์เมทริกซ์ดอท

เนื่องจากไม่มีใครตอบคำถามเลยฉันจะให้มันยิง ก่อนอื่นฉันต้องการเติมผืนผ้าใบด้วยเส้นโค้งของฮิลแบร์ต แต่ในที่สุดฉันก็เลือกวิธีที่ง่ายกว่า:

สไตล์เมทริกซ์โมนาลิซ่า

นี่คือรหัส:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class LineArt extends JPanel {
    private BufferedImage ref;
    //Images are stored in integers:
    int[] images = new int[] {31, 475, 14683, 469339};
    int[] brightness = new int[] {200,170,120,0};

    public static void main(String[] args) throws Exception {
        new LineArt(args[0]);
    }

    public LineArt(String filename) throws Exception {
        ref = ImageIO.read(new File(filename));
        JFrame frame = new JFrame();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(ref.getWidth()*5, ref.getHeight()*5);
        this.setPreferredSize(new Dimension((ref.getWidth()*5)+20, (ref.getHeight()*5)+20));
        frame.add(new JScrollPane(this));
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, getWidth(), getHeight());
        g2d.translate(10, 10);
        g2d.setColor(Color.BLACK);
        g2d.drawLine(0, 0, 4, 0);
        g2d.drawLine(0, 0, 0, ref.getHeight()*5);

        for(int y = 0; y<ref.getHeight();y++) {
            for(int x = 1; x<ref.getWidth()-1;x++) {
                int light = new Color(ref.getRGB(x, y)).getRed();
                int offset = 0;
                while(brightness[offset]>light) offset++;
                for(int i = 0; i<25;i++) {
                    if((images[offset]&1<<i)>0) {
                        g2d.drawRect((x*5)+i%5, (y*5)+(i/5), 0,0);
                    }
                }
            }
            g2d.drawLine(2, (y*5), 4, (y*5));
            g2d.drawLine((ref.getWidth()*5)-5, (y*5), (ref.getWidth()*5)-1, (y*5));
            if(y%2==0) {
                g2d.drawLine((ref.getWidth()*5)-1, (y*5), (ref.getWidth()*5)-1, (y*5)+4);
            } else {
                g2d.drawLine(2, (y*5), 2, (y*5)+4);
            }
        }
        if(ref.getHeight()%2==0) {
            g2d.drawLine(0, ref.getHeight()*5, 2, ref.getHeight()*5);
        } else {
            g2d.drawLine(0, ref.getHeight()*5, (ref.getWidth()*5)-1, ref.getHeight()*5);
        }
    }
}

อัปเดต : ตอนนี้มันสร้างรอบไม่ใช่แค่บรรทัดเดียว


2
วิธีแก้ปัญหาที่ดีมากและเรียบง่ายฉันไม่ได้คิดวิธีแก้ปัญหา แต่มันก็ดูดี!
ข้อบกพร่อง

@DenDenDo แนะนำให้พล็อตแอนิเมชั่นการไหลของกราฟเพื่อทำให้เส้นโค้งสั้นลง มันจะดีถ้าคุณสามารถให้ textfile (csv หรืออะไรก็ได้ที่คุณต้องการ) พร้อมพิกัดของจุด conrner ทั้งหมดที่คุณใช้ในลำดับที่ถูกต้อง ฉันทำสคริปต์ MATLAB สำหรับการคำนวณการเคลื่อนไหว - แต่แน่นอนคุณยังมีอิสระที่จะทำมันด้วยตัวเอง =)
flawr

35

Python: ส่วนโค้งของ Hilbert ( 373 361)

ฉันตัดสินใจวาดเส้นโค้งของฮิลแบร์ตด้วยความละเอียดหลายระดับขึ้นอยู่กับความเข้มของภาพ:

import pylab as pl
from scipy.misc import imresize, imfilter
import turtle

# load image
img = pl.flipud(pl.imread("face.png"))

# setup turtle
levels = 8
size = 2**levels
turtle.setup(img.shape[1] * 4.2, img.shape[0] * 4.2)
turtle.setworldcoordinates(0, 0, size, -size)
turtle.tracer(1000, 0)

# resize and blur image
img = imfilter(imresize(img, (size, size)), 'blur')

# define recursive hilbert curve
def hilbert(level, angle = 90):
    if level == 0:
        return
    if level == 1 and img[-turtle.pos()[1], turtle.pos()[0]] > 128:
        turtle.forward(2**level - 1)
    else:
        turtle.right(angle)
        hilbert(level - 1, -angle)
        turtle.forward(1)
        turtle.left(angle)
        hilbert(level - 1, angle)
        turtle.forward(1)
        hilbert(level - 1, angle)
        turtle.left(angle)
        turtle.forward(1)
        hilbert(level - 1, -angle)
        turtle.right(angle)

# draw hilbert curve
hilbert(levels)
turtle.update()

ที่จริงฉันวางแผนที่จะตัดสินใจเกี่ยวกับรายละเอียดในระดับต่าง ๆ เช่น "จุดนี้สว่างมากฉันจะหยุดการเรียกซ้ำและย้ายไปยังบล็อกถัดไป!" แต่การประเมินความเข้มของภาพในพื้นที่ที่นำไปสู่การเคลื่อนไหวขนาดใหญ่นั้นไม่ถูกต้องและดูน่าเกลียดมาก ดังนั้นฉันจึงตัดสินใจเพียงแค่ว่าจะข้ามระดับ 1 หรือวาดอีกวงของฮิลแบร์ต

นี่คือผลการทดสอบภาพแรก:

ผลลัพธ์

ขอบคุณ @githubphagocyte การแสดงผลค่อนข้างเร็ว (ใช้turtle.tracer) ดังนั้นฉันไม่ต้องรอทั้งคืนเพื่อผลลัพธ์และสามารถไปที่เตียงที่ดีของฉัน :)


รหัสกอล์ฟบางอย่าง

@flawr: "โปรแกรมสั้น" หรือไม่ คุณยังไม่เห็นเวอร์ชั่น golfed! ;)

ดังนั้นเพื่อความสนุก:

from pylab import*;from scipy.misc import*;from turtle import*
i=imread("f.p")[::-1];s=256;h=i.shape;i=imfilter(imresize(i,(s,s)),'blur')
setup(h[1]*4.2,h[0]*4.2);setworldcoordinates(0,0,s,-s);f=forward;r=right
def h(l,a=90):
 x,y=pos()
 if l==1and i[-y,x]>128:f(2**l-1)
 else:
  if l:l-=1;r(a);h(l,-a);f(1);r(-a);h(l,a);f(1);h(l,a);r(-a);f(1);h(l,-a);r(a)
h(8)

( 373 361 ตัวอักษร แต่มันจะใช้เวลาตลอดไปตั้งแต่ฉันลบturte.tracer(...)คำสั่ง!)


แอนิเมชันโดยข้อบกพร่อง

ข้อบกพร่อง: อัลกอริทึมของฉันได้รับการแก้ไขเล็กน้อยกับสิ่งที่ @DenDenDo บอกฉัน: ฉันต้องลบบางจุดในการวนซ้ำทุกครั้งเนื่องจากการบรรจบกันจะช้าลงอย่างมาก นั่นเป็นสาเหตุที่ทำให้เส้นโค้งตัดกัน

ป้อนคำอธิบายรูปภาพที่นี่


1
ทำได้ดีมาก! หากคุณต้องการทำงานได้เร็วขึ้นลองแทนscreen.tracer(0) turtle.speed(0)คุณอาจต้องยกตัวอย่างหน้าจอเมื่อเริ่มต้น แต่ถ้าเป็นเพียงหน้าจอเดียวเต่าทั้งหมดของคุณจะถูกกำหนดให้โดยอัตโนมัติ จากนั้นเพียงแค่screen.update()ในตอนท้ายเพื่อแสดงผลลัพธ์ ฉันประหลาดใจที่ความเร็วแตกต่างกันเมื่อครั้งแรกที่ผมค้นพบนี้ ...
Trichoplax

ฉันรู้สึกประหลาดใจจริง ๆ ที่คุณสามารถทำได้ในโปรแกรมสั้น ๆ ! แต่อย่างไรก็ตามขอแสดงความยินดี! fractals ftw =)
ข้อผิดพลาด

@DenDenDo แนะนำให้พล็อตแอนิเมชั่นการไหลของกราฟเพื่อทำให้เส้นโค้งสั้นลง มันจะดีถ้าคุณสามารถให้ textfile (csv หรืออะไรก็ได้ที่คุณต้องการ) พร้อมพิกัดของจุด conrner ทั้งหมดที่คุณใช้ในลำดับที่ถูกต้อง ฉันทำสคริปต์ MATLAB สำหรับการคำนวณการเคลื่อนไหว - แต่แน่นอนคุณยังมีอิสระที่จะทำมันด้วยตัวเอง =)
flawr

@flawr: ที่นี่เราจะไป
Falko

ดังนั้นนี่คือรหัส: pastebin.com/wTcwb0nm
flawr

32

Python 3.4 - ปัญหาการเดินทางของพนักงานขาย

โปรแกรมสร้างภาพ dithered จากต้นฉบับ:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

สำหรับแต่ละพิกเซลสีดำจุดที่มีการสร้างแบบสุ่มที่อยู่ใกล้กับศูนย์พิกเซลและจุดเหล่านี้จะถือว่าเป็นปัญหาพนักงานขายที่เดินทาง โปรแกรมจะบันทึกไฟล์ html ที่มีอิมเมจ SVG เป็นระยะอย่างสม่ำเสมอเนื่องจากพยายามลดความยาวของพา ธ เส้นทางเริ่มจากการตัดกันด้วยตนเองและค่อยๆกลายเป็นน้อยกว่าหลายชั่วโมง ในที่สุดเส้นทางก็ไม่ตัดกันอีกต่อไป:

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

'''
Traveling Salesman image approximation.
'''

import os.path

from PIL import Image   # This uses Pillow, the PIL fork for Python 3.4
                        # https://pypi.python.org/pypi/Pillow

from random import random, sample, randrange, shuffle
from time import perf_counter


def make_line_picture(image_filename):
    '''Save SVG image of closed curve approximating input image.'''
    input_image_path = os.path.abspath(image_filename)
    image = Image.open(input_image_path)
    width, height = image.size
    scale = 1024 / width
    head, tail = os.path.split(input_image_path)
    output_tail = 'TSP_' + os.path.splitext(tail)[0] + '.html'
    output_filename = os.path.join(head, output_tail)
    points = generate_points(image)
    population = len(points)
    save_dither(points, image)
    grid_cells = [set() for i in range(width * height)]
    line_cells = [set() for i in range(population)]
    print('Initialising acceleration grid')
    for i in range(population):
        recalculate_cells(i, width, points, grid_cells, line_cells)
    while True:
        save_svg(output_filename, width, height, points, scale)
        improve_TSP_solution(points, width, grid_cells, line_cells)


def save_dither(points, image):
    '''Save a copy of the dithered image generated for approximation.'''
    image = image.copy()
    pixels = list(image.getdata())
    pixels = [255] * len(pixels)
    width, height = image.size
    for p in points:
        x = int(p[0])
        y = int(p[1])
        pixels[x+y*width] = 0
    image.putdata(pixels)
    image.save('dither_test.png', 'PNG')


def generate_points(image):
    '''Return a list of points approximating the image.

    All points are offset by small random amounts to prevent parallel lines.'''
    width, height = image.size
    image = image.convert('L')
    pixels = image.getdata()
    points = []
    gap = 1
    r = random
    for y in range(2*gap, height - 2*gap, gap):
        for x in range(2*gap, width - 2*gap, gap):
            if (r()+r()+r()+r()+r()+r())/6 < 1 - pixels[x + y*width]/255:
                        points.append((x + r()*0.5 - 0.25,
                                       y + r()*0.5 - 0.25))
    shuffle(points)
    print('Total number of points', len(points))
    print('Total length', current_total_length(points))
    return points


def current_total_length(points):
    '''Return the total length of the current closed curve approximation.'''
    population = len(points)
    return sum(distance(points[i], points[(i+1)%population])
               for i in range(population))


def recalculate_cells(i, width, points, grid_cells, line_cells):
    '''Recalculate the grid acceleration cells for the line from point i.'''
    for j in line_cells[i]:
        try:
            grid_cells[j].remove(i)
        except KeyError:
            print('grid_cells[j]',grid_cells[j])
            print('i',i)
    line_cells[i] = set()
    add_cells_along_line(i, width, points, grid_cells, line_cells)
    for j in line_cells[i]:
        grid_cells[j].add(i)


def add_cells_along_line(i, width, points, grid_cells, line_cells):
    '''Add each grid cell that lies on the line from point i.'''
    population = len(points)
    start_coords = points[i]
    start_x, start_y = start_coords
    end_coords = points[(i+1) % population]
    end_x, end_y = end_coords
    gradient = (end_y - start_y) / (end_x - start_x)
    y_intercept = start_y - gradient * start_x
    total_distance = distance(start_coords, end_coords)
    x_direction = end_x - start_x
    y_direction = end_y - start_y
    x, y = start_x, start_y
    grid_x, grid_y = int(x), int(y)
    grid_index = grid_x + grid_y * width
    line_cells[i].add(grid_index)
    while True:
        if x_direction > 0:
            x_line = int(x + 1)
        else:
            x_line = int(x)
            if x_line == x:
                x_line = x - 1
        if y_direction > 0:
            y_line = int(y + 1)
        else:
            y_line = int(y)
            if y_line == y:
                y_line = y - 1
        x_line_intersection = gradient * x_line + y_intercept
        y_line_intersection = (y_line - y_intercept) / gradient
        x_line_distance = distance(start_coords, (x_line, x_line_intersection))
        y_line_distance = distance(start_coords, (y_line_intersection, y_line))
        if (x_line_distance > total_distance and
            y_line_distance > total_distance):
            break
        if x_line_distance < y_line_distance:
            x = x_line
            y = gradient * x_line + y_intercept
        else:
            y = y_line
            x = (y_line - y_intercept) / gradient
        grid_x = int(x - (x_direction < 0) * (x == int(x)))
        grid_y = int(y - (y_direction < 0) * (y == int(y)))
        grid_index = grid_x + grid_y * width
        line_cells[i].add(grid_index)


def improve_TSP_solution(points, width, grid_cells, line_cells,
                         performance=[0,0,0], total_length=None):
    '''Apply 3 approaches, allocating time to each based on performance.'''
    population = len(points)
    if total_length is None:
        total_length = current_total_length(points)

    print('Swapping pairs of vertices')
    if performance[0] == max(performance):
        time_limit = 300
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        swap_two_vertices(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time
    old_length = total_length
    total_length = current_total_length(points)
    performance[0] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[0])

    print('Moving single vertices')
    if performance[1] == max(performance):
        time_limit = 300
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        move_a_single_vertex(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time
    old_length = total_length
    total_length = current_total_length(points)
    performance[1] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[1])

    print('Uncrossing lines')
    if performance[2] == max(performance):
        time_limit = 60
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        uncross_lines(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time        
    old_length = total_length
    total_length = current_total_length(points)
    performance[2] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[2])


def swap_two_vertices(points, width, grid_cells, line_cells):
    '''Attempt to find a pair of vertices that reduce length when swapped.'''
    population = len(points)
    for n in range(100):
        candidates = sample(range(population), 2)
        befores = [(candidates[i] - 1) % population
                   for i in (0,1)]
        afters = [(candidates[i] + 1) % population for i in (0,1)]
        current_distance = sum((distance(points[befores[i]],
                                         points[candidates[i]]) +
                                distance(points[candidates[i]],
                                         points[afters[i]]))
                               for i in (0,1))
        (points[candidates[0]],
         points[candidates[1]]) = (points[candidates[1]],
                                   points[candidates[0]])
        befores = [(candidates[i] - 1) % population
                   for i in (0,1)]
        afters = [(candidates[i] + 1) % population for i in (0,1)]
        new_distance = sum((distance(points[befores[i]],
                                     points[candidates[i]]) +
                            distance(points[candidates[i]],
                                     points[afters[i]]))
                           for i in (0,1))
        if new_distance > current_distance:
            (points[candidates[0]],
             points[candidates[1]]) = (points[candidates[1]],
                                       points[candidates[0]])
        else:
            modified_points = tuple(set(befores + candidates))
            for k in modified_points:
                recalculate_cells(k, width, points, grid_cells, line_cells)
            return


def move_a_single_vertex(points, width, grid_cells, line_cells):
    '''Attempt to find a vertex that reduces length when moved elsewhere.'''
    for n in range(100):
        population = len(points)
        candidate = randrange(population)
        offset = randrange(2, population - 1)
        new_location = (candidate + offset) % population
        before_candidate = (candidate - 1) % population
        after_candidate = (candidate + 1) % population
        before_new_location = (new_location - 1) % population
        old_distance = (distance(points[before_candidate], points[candidate]) +
                        distance(points[candidate], points[after_candidate]) +
                        distance(points[before_new_location],
                                 points[new_location]))
        new_distance = (distance(points[before_candidate],
                                 points[after_candidate]) +
                        distance(points[before_new_location],
                                 points[candidate]) +
                        distance(points[candidate], points[new_location]))
        if new_distance <= old_distance:
            if new_location < candidate:
                points[:] = (points[:new_location] +
                             points[candidate:candidate + 1] +
                             points[new_location:candidate] +
                             points[candidate + 1:])
                for k in range(candidate - 1, new_location, -1):
                    for m in line_cells[k]:
                        grid_cells[m].remove(k)
                    line_cells[k] = line_cells[k - 1]
                    for m in line_cells[k]:
                        grid_cells[m].add(k)
                for k in ((new_location - 1) % population,
                          new_location, candidate):
                    recalculate_cells(k, width, points, grid_cells, line_cells)
            else:
                points[:] = (points[:candidate] +
                             points[candidate + 1:new_location] +
                             points[candidate:candidate + 1] +
                             points[new_location:])
                for k in range(candidate, new_location - 3):
                    for m in line_cells[k]:
                        grid_cells[m].remove(k)
                    line_cells[k] = line_cells[k + 1]
                    for m in line_cells[k]:
                        grid_cells[m].add(k)
                for k in ((candidate - 1) % population,
                          new_location - 2, new_location - 1):
                    recalculate_cells(k, width, points, grid_cells, line_cells)
            return


def uncross_lines(points, width, grid_cells, line_cells):
    '''Attempt to find lines that are crossed, and reverse path to uncross.'''
    population = len(points)
    for n in range(100):
        i = randrange(population)
        start_1 = points[i]
        end_1 = points[(i + 1) % population]
        if not line_cells[i]:
            recalculate_cells(i, width, points, grid_cells, line_cells)
        for cell in line_cells[i]:
            for j in grid_cells[cell]:
                if i != j and i != (j+1)%population and i != (j-1)%population:
                    start_2 = points[j]
                    end_2 = points[(j + 1) % population]
                    if are_crossed(start_1, end_1, start_2, end_2):
                        if i < j:
                            points[i + 1:j + 1] = reversed(points[i + 1:j + 1])
                            for k in range(i, j + 1):
                                recalculate_cells(k, width, points, grid_cells,
                                                  line_cells)
                        else:
                            points[j + 1:i + 1] = reversed(points[j + 1:i + 1])
                            for k in range(j, i + 1):
                                recalculate_cells(k, width, points, grid_cells,
                                                  line_cells)
                        return


def are_crossed(start_1, end_1, start_2, end_2):
    '''Return True if the two lines intersect.'''
    if end_1[0]-start_1[0] and end_2[0]-start_2[0]:
        gradient_1 = (end_1[1]-start_1[1])/(end_1[0]-start_1[0])
        gradient_2 = (end_2[1]-start_2[1])/(end_2[0]-start_2[0])
        if gradient_1-gradient_2:
            intercept_1 = start_1[1] - gradient_1 * start_1[0]
            intercept_2 = start_2[1] - gradient_2 * start_2[0]        
            x = (intercept_2 - intercept_1) / (gradient_1 - gradient_2)
            if (x-start_1[0]) * (end_1[0]-x) > 0 and (x-start_2[0]) * (end_2[0]-x) > 0:
                return True


def distance(point_1, point_2):
    '''Return the Euclidean distance between the two points.'''
    return sum((point_1[i] - point_2[i]) ** 2 for i in (0, 1)) ** 0.5


def save_svg(filename, width, height, points, scale):
    '''Save a file containing an SVG path of the points.'''
    print('Saving partial solution\n')
    with open(filename, 'w') as file:
        file.write(content(width, height, points, scale))


def content(width, height, points, scale):
    '''Return the full content to be written to the SVG file.'''
    return (header(width, height, scale) +
            specifics(points, scale) +
            footer()
            )


def header(width, height,scale):
    '''Return the text of the SVG header.'''
    return ('<?xml version="1.0"?>\n'
            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"\n'
            '    "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n'
            '\n'
            '<svg width="{0}" height="{1}">\n'
            '<title>Traveling Salesman Problem</title>\n'
            '<desc>An approximate solution to the Traveling Salesman Problem</desc>\n'
            ).format(scale*width, scale*height)


def specifics(points, scale):
    '''Return text for the SVG path command.'''
    population = len(points)
    x1, y1 = points[-1]
    x2, y2 = points[0]
    x_mid, y_mid = (x1 + x2) / 2, (y1 + y2) / 2
    text = '<path d="M{},{} L{},{} '.format(x1, y1, x2, y2)
    for i in range(1, population):
        text += 'L{},{} '.format(*points[i])
    text += '" stroke="black" fill="none" stroke-linecap="round" transform="scale({0},{0})" vector-effect="non-scaling-stroke" stroke-width="3"/>'.format(scale)
    return text


def footer():
    '''Return the closing text of the SVG file.'''
    return '\n</svg>\n'


if __name__ == '__main__':
    import sys
    arguments = sys.argv[1:]
    if arguments:
        make_line_picture(arguments[0])
    else:
        print('Required argument: image file')

โปรแกรมใช้วิธีที่แตกต่างกัน 3 วิธีในการปรับปรุงโซลูชันและวัดประสิทธิภาพต่อวินาทีสำหรับแต่ละวิธี เวลาที่จัดสรรให้กับแต่ละวิธีจะถูกปรับเพื่อให้เวลาส่วนใหญ่กับวิธีการใดก็ตามที่มีประสิทธิภาพดีที่สุดในเวลานั้น

ตอนแรกฉันพยายามคาดเดาว่าเวลาใดที่จะจัดสรรให้แต่ละวิธี แต่สัดส่วนกลับกลายเป็นว่าวิธีการใดที่มีประสิทธิภาพมากที่สุดนั้นแตกต่างกันอย่างมากในระหว่างกระบวนการดังนั้นมันจึงสร้างความแตกต่างอย่างมากในการปรับอัตโนมัติ

สามวิธีง่ายๆคือ:

  1. เลือกจุดสองจุดโดยการสุ่มและสลับหากไม่เพิ่มความยาวทั้งหมด
  2. เลือกจุดหนึ่งโดยการสุ่มและการสุ่มแบบสุ่มไปตามรายการของจุดและย้ายไปหากความยาวไม่เพิ่ม
  3. เลือกบรรทัดโดยการสุ่มและตรวจสอบว่ามีเส้นอื่นข้ามหรือไม่กลับส่วนของเส้นทางที่ทำให้เกิดการข้าม

สำหรับวิธีที่ 3 จะใช้กริดรายการทั้งหมดของบรรทัดที่ส่งผ่านเซลล์ที่กำหนด แทนที่จะต้องตรวจสอบทุกบรรทัดบนหน้าเพื่อหาจุดตัดกันเฉพาะผู้ที่มีเซลล์กริดร่วมกันเท่านั้นที่จะถูกตรวจสอบ


ฉันมีความคิดที่จะใช้ปัญหาพนักงานขายที่เดินทางจากบล็อกโพสต์ที่ฉันเห็นก่อนที่ความท้าทายนี้จะโพสต์ แต่ฉันไม่สามารถติดตามมันได้เมื่อฉันโพสต์คำตอบนี้ ฉันเชื่อว่าภาพในการท้าทายถูกสร้างขึ้นโดยใช้วิธีการของพนักงานขายที่เดินทางเช่นกันรวมกับเส้นทางที่ราบเรียบบางชนิดเพื่อกำจัดการเลี้ยวที่คมชัด

ฉันยังไม่สามารถหาสิ่งที่โพสต์บล็อกที่เฉพาะเจาะจง แต่ตอนนี้ฉันได้พบการอ้างอิงถึงเอกสารต้นฉบับที่โมนาลิซ่าถูกใช้ในการแสดงให้เห็นถึงปัญหาการเดินทางพนักงานขาย

การนำ TSP ไปใช้ที่นี่เป็นวิธีไฮบริดที่ฉันทดลองเพื่อความสนุกสำหรับความท้าทายนี้ ฉันไม่ได้อ่านเอกสารที่เชื่อมโยงเมื่อฉันโพสต์สิ่งนี้ วิธีการของฉันช้ากว่าการเปรียบเทียบอย่างเจ็บปวด โปรดทราบว่าภาพของฉันที่นี่ใช้น้อยกว่า 10,000 คะแนนและใช้เวลาหลายชั่วโมงในการรวมเข้าด้วยกันมากพอที่จะไม่มีทางข้าม ภาพตัวอย่างในลิงก์ไปยังเอกสารใช้ 100,000 คะแนน ...

น่าเสียดายที่ลิงค์ส่วนใหญ่ดูเหมือนจะตายไปแล้วในตอนนี้ แต่บทความ "TSP Art" ของ Craig S Kaplan และ Robert Bosch 2005ยังคงใช้งานได้และให้ภาพรวมที่น่าสนใจเกี่ยวกับวิธีการที่แตกต่างกัน


1
ว้าวนั่นเป็นสิ่งที่ดีจริงๆ =) (หากคุณต้องการให้ฉันทำแอนิเมชั่นการไหลของเส้นโค้งที่สั้นลงเช่นกันเพียงแค่ให้ csv หรือสิ่งที่คล้ายกับรายการสั่งพิกัดพิกัด)
ข้อผิดพลาด

@ flawr ขอบคุณ! สำหรับรายการพิกัดจุดสั่งซื้อมันเกือบ 10,000 คะแนนสำหรับใบหน้า mona lisa มันใกล้จะถึง 100,000 คะแนนสำหรับภาพที่ใหญ่ขึ้น นั่นเป็นเหตุผลที่ฉันยังไม่ได้โพสต์ข้อความ SVG ที่นี่ ... :)
trichoplax

คุณสามารถใช้ pastebin.com หรืออะไรที่คล้ายกัน แต่ฉันไม่ต้องการบังคับคุณมันเป็นการตัดสินใจของคุณ (ฉันไม่เก่งใน Python =)
ข้อบกพร่อง

@flawr ฉันไม่ต้องการให้คุณต้องรอชั่วโมงและชั่วโมงเพื่อให้โปรแกรมทำงาน ฉันจะไม่ได้รับการเพิ่มภาพเคลื่อนไหวไหลให้คำตอบของฉัน แต่ถ้าคุณต้องการจุดของตัวเองให้ฉันรู้และฉันสามารถหาที่จะโพสต์พวกเขา ...
Trichoplax

ฉันไม่เคยมีความคิดเกี่ยวกับ TSP เลย! รับ upvote!
sergiol

24

Java - ความผันผวน

โปรแกรมดึงเส้นทางที่ปิดและเพิ่มการแกว่งซึ่งมีแอมพลิจูดและความถี่ขึ้นอยู่กับความสว่างของภาพ "มุม" ของเส้นทางไม่มีการแกว่งเพื่อให้แน่ใจว่าเส้นทางไม่ได้ตัดกัน

ป้อนคำอธิบายรูปภาพที่นี่

package trace;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import snake.Image;

public class Main5 {


    private final static int MULT = 3;
    private final static int ROWS = 80; // must be an even number
    private final static int COLS = 40;

    public static void main(String[] args) throws IOException {
        BufferedImage src = ImageIO.read(Image.class.getClassLoader().getResourceAsStream("input.png"));
        BufferedImage dest = new BufferedImage(src.getWidth()*MULT, src.getHeight()*MULT, BufferedImage.TYPE_INT_RGB);

        int [] white = {255, 255, 255};
        for (int y = 0; y < dest.getHeight(); y++) {
            for (int x = 0; x < dest.getWidth(); x++) {
                dest.getRaster().setPixel(x, y, white);
            }
        }
        for (int j = 0; j < ROWS; j++) {
            if (j%2 == 0) {
                for (int i = j==0 ? 0 : 1; i < COLS-1; i++) {
                    drawLine(dest, src, (i+.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (i+1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS,
                            i > 1 && i < COLS-2);
                }

                drawLine(dest, src, (COLS-.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (COLS-.5)*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
            } else {
                for (int i = COLS-2; i >= (j == ROWS - 1 ? 0 : 1); i--) {
                    drawLine(dest, src, (i+.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (i+1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS,
                            i > 1 && i < COLS-2);
                }
                if (j < ROWS-1) {
                    drawLine(dest, src, (1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (1.5)*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
                }
            }
            if (j < ROWS-1) {
                drawLine(dest, src, 0.5*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, 0.5*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
            }
        }
        ImageIO.write(dest, "png", new File("output.png"));
    }

    private static void drawLine(BufferedImage dest, BufferedImage src, double x1, double y1, double x2, double y2, boolean oscillate) {
        int [] black = {0, 0, 0};

        int col = smoothPixel((int)((x1*.5 + x2*.5) / MULT), (int)((y1*.5+y2*.5) / MULT), src);
        int fact = (255 - col) / 32;
        if (fact > 5) fact = 5;
        double dx = y1 - y2;
        double dy = - (x1 - x2);
        double dist = 2 * (Math.abs(x1 - x2) + Math.abs(y1 - y2)) * (fact + 1);
        for (int i = 0; i <= dist; i++) {
            double amp = oscillate ? (1 - Math.cos(fact * i*Math.PI*2/dist)) * 12 : 0;
            double x = (x1 * i + x2 * (dist - i)) / dist;
            double y = (y1 * i + y2 * (dist - i)) / dist;
            x += dx * amp / COLS;
            y += dy * amp / ROWS;
            dest.getRaster().setPixel((int)x, (int)y, black);
        }
    }

    public static int smoothPixel(int x, int y, BufferedImage src) {
        int sum = 0, count = 0;
        for (int j = -2; j <= 2; j++) {
            for (int i = -2; i <= 2; i++) {
                if (x + i >= 0 && x + i < src.getWidth()) {
                    if (y + j >= 0 && y + j < src.getHeight()) {
                        sum += src.getRGB(x + i, y + j) & 255;
                        count++;
                    }
                }
            }
        }
        return sum / count;
    }
}

ด้านล่างอัลกอริทึมที่เปรียบเทียบได้ซึ่งตั้งอยู่บนเกลียว ( ฉันรู้ว่าเส้นทางไม่ได้ปิดและแน่นอนว่ามันตัดกันฉันเพิ่งโพสต์ไว้เพื่อประโยชน์ของศิลปะ :-)

ป้อนคำอธิบายรูปภาพที่นี่


ฉันชอบเอฟเฟกต์การมองเห็นของเกลียว!
จะ

ฉันเหมือนกันขอบคุณสำหรับการแบ่งปัน! (ถ้าคุณต้องการคุณยังสามารถทำรายการสั่งซื้อของจุดเส้นทางและฉันจะดูว่าฉันจะทำภาพเคลื่อนไหวกับคนนี้มากเกินไป =)
flawr

@ github ขอบคุณสำหรับความคิดเห็นที่สร้างสรรค์ของคุณ
Arnaud

1
+1 จากฉัน - เป็นไปตามกฎอย่างสมบูรณ์แบบในตอนนี้และฉันชอบการเปลี่ยนที่ราบรื่นที่ความถี่เปลี่ยนให้
trichoplax

21

Java - เส้นทางแบบเรียกซ้ำ

ฉันเริ่มต้นจากเส้นทางปิด 2x3 ฉันสแกนแต่ละเซลล์ของเส้นทางและแบ่งออกเป็นเส้นทางย่อย 3x3 ใหม่ ฉันลองแต่ละครั้งเพื่อเลือกเส้นทางย่อย 3x3 ที่ "ดูเหมือน" ภาพต้นฉบับ ฉันทำซ้ำกระบวนการข้างต้น 4 ครั้ง

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัส:

package divide;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import snake.Image;

public class Divide {

    private final static int MULT = 3;
    private final static int ITERATIONS = 4;

    public static void main(String[] args) throws IOException {
        BufferedImage src = ImageIO.read(Image.class.getClassLoader().getResourceAsStream("input.png"));
        BufferedImage dest = new BufferedImage(src.getWidth() * MULT, src.getHeight() * MULT, BufferedImage.TYPE_INT_RGB);
        for (int y = 0; y < src.getHeight() * MULT; y++) {
            for (int x = 0; x < src.getWidth() * MULT; x++) {
                dest.getRaster().setPixel(x, y, new int [] {255, 255, 255});
            }
        }
        List<String> tab = new ArrayList<String>();
        tab.add("rg");
        tab.add("||"); 
        tab.add("LJ");

        for (int k = 1; k <= ITERATIONS; k++) {
            boolean choose = k>=ITERATIONS-1;
            // multiply size by 3
            tab = iterate(src, tab, choose);
            // fill in the white space - if needed
            expand(src, tab, " r", " L", "r-", "L-", choose);
            expand(src, tab, "g ", "J ", "-g", "-J", choose);
            expand(src, tab, "LJ", "  ", "||", "LJ", choose);
            expand(src, tab, "  ", "rg", "rg", "||", choose);
            expand(src, tab, "L-J", "   ", "| |", "L-J", choose);
            expand(src, tab, "   ", "r-g", "r-g", "| |", choose);
            expand(src, tab, "| |", "| |", "Lg|", "rJ|", choose);
            expand(src, tab, "--", "  ", "gr", "LJ", choose);
            expand(src, tab, "  ", "--", "rg", "JL", choose);
            expand(src, tab, "| ", "| ", "Lg", "rJ", choose);
            expand(src, tab, " |", " |", "rJ", "Lg", choose);

            for (String s : tab) {
                System.out.println(s);
            }
            System.out.println();
        }

        for (int j = 0; j < tab.size(); j++) {
            String line = tab.get(j);
            for (int i = 0; i < line.length(); i++) {
                char c = line.charAt(i);
                int xleft = i * dest.getWidth() / line.length();
                int xright = (i+1) * dest.getWidth() / line.length();
                int ytop = j * dest.getHeight() / tab.size();
                int ybottom = (j+1) * dest.getHeight() / tab.size();
                int x = (xleft + xright) / 2;
                int y = (ytop + ybottom) / 2;
                if (c == '|') {
                    drawLine(dest, x, ytop, x, ybottom);
                }
                if (c == '-') {
                    drawLine(dest, xleft, y, xright, y);
                }
                if (c == 'L') {
                    drawLine(dest, x, y, xright, y);
                    drawLine(dest, x, y, x, ytop);
                }
                if (c == 'J') {
                    drawLine(dest, x, y, xleft, y);
                    drawLine(dest, x, y, x, ytop);
                }
                if (c == 'r') {
                    drawLine(dest, x, y, xright, y);
                    drawLine(dest, x, y, x, ybottom);
                }
                if (c == 'g') {
                    drawLine(dest, x, y, xleft, y);
                    drawLine(dest, x, y, x, ybottom);
                }
            }

        }

        ImageIO.write(dest, "png", new File("output.png"));

    }


    private static void drawLine(BufferedImage dest, int x1, int y1, int x2, int y2) {
        int dist = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
        for (int i = 0; i <= dist; i++) {
            int x = (x1*(dist - i) + x2 * i) / dist;
            int y = (y1*(dist - i) + y2 * i) / dist;
            dest.getRaster().setPixel(x, y, new int [] {0, 0, 0});
        }
    }

    private static void expand(BufferedImage src, List<String> tab, String p1, String p2, String r1, String r2, boolean choose) {
        for (int k = 0; k < (choose ? 2 : 1); k++) {
            while (true) {
                boolean again = false;
                for (int j = 0; j < tab.size() - 1; j++) {
                    String line1 = tab.get(j);
                    String line2 = tab.get(j+1);
                    int baseScore = evaluateLine(src, j, tab.size(), line1) + evaluateLine(src, j+1, tab.size(), line2);
                    for (int i = 0; i <= line1.length() - p1.length(); i++) {
                        if (line1.substring(i, i + p1.length()).equals(p1)
                                && line2.substring(i, i + p2.length()).equals(p2)) {
                            String nline1 = line1.substring(0,  i) + r1 + line1.substring(i + p1.length());
                            String nline2 = line2.substring(0,  i) + r2 + line2.substring(i + p2.length());
                            int nScore = evaluateLine(src, j, tab.size(), nline1) + evaluateLine(src, j+1, tab.size(), nline2);
                            if (!choose || nScore > baseScore) {
                                tab.set(j, nline1);
                                tab.set(j+1, nline2);
                                again = true;
                                break;
                            }
                        }
                    }
                    if (again) break;
                }
                if (!again) break;
            }
            String tmp1 = r1;
            String tmp2 = r2;
            r1 = p1;
            r2 = p2;
            p1 = tmp1;
            p2 = tmp2;
        }
    }

    private static int evaluateLine(BufferedImage src, int j, int tabSize, String line) {
        int [] color = {0, 0, 0};
        int score = 0;
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            int x = i*src.getWidth() / line.length();
            int y = j*src.getHeight() / tabSize;
            src.getRaster().getPixel(x, y, color);
            if (c == ' ' && color[0] >= 128) score++;
            if (c != ' ' && color[0] < 128) score++;
        }
        return score;
    }



    private static List<String> iterate(BufferedImage src, List<String> tab, boolean choose) {
        int [] color = {0, 0, 0};
        List<String> tab2 = new ArrayList<String>();
        for (int j = 0; j < tab.size(); j++) {
            String line = tab.get(j);
            String l1 = "", l2 = "", l3 = "";
            for (int i = 0; i < line.length(); i++) {
                char c = line.charAt(i);
                List<String []> candidates = replace(c);
                String [] choice = null;
                if (choose) {

                    int best = 0;
                    for (String [] candidate : candidates) {
                        int bright1 = 0;
                        int bright2 = 0;
                        for (int j1 = 0; j1<3; j1++) {
                            int y = j*3+j1;
                            for (int i1 = 0; i1<3; i1++) {
                                int x = i*3+i1;
                                char c2 = candidate[j1].charAt(i1);
                                src.getRaster().getPixel(x*src.getWidth()/(line.length()*3), y*src.getHeight()/(tab.size()*3), color);
                                if (c2 != ' ') bright1++;
                                if (color[0] > 128) bright2++;
                            }
                        }
                        int score = Math.abs(bright1 - bright2);
                        if (choice == null || score > best) {
                            best = score;
                            choice = candidate;
                        }

                    }
                } else {
                    choice = candidates.get(0);
                }
                //String [] r = candidates.get(rand.nextInt(candidates.size()));
                String [] r = choice;
                l1 += r[0];
                l2 += r[1];
                l3 += r[2];
            }
            tab2.add(l1);
            tab2.add(l2);
            tab2.add(l3);
        }
        return tab2;
    }

    private static List<String []> replace(char c) {
        if (c == 'r') {
            return Arrays.asList(
                    new String[] {
                    "r-g",
                    "| L",
                    "Lg "},
                    new String[] {
                    "   ",
                    " r-",
                    " | "}, 
                    new String[] {
                    "   ",
                    "r--",
                    "Lg "}, 
                    new String[] {
                    " rg",
                    " |L",
                    " | "},
                    new String[] {
                    "   ",
                    "  r",
                    " rJ"});            
        } else if (c == 'g') {
            return Arrays.asList(
                    new String[] {
                    "r-g",
                    "J |",
                    " rJ"},                 
                    new String[] {
                    "   ",
                    "-g ",
                    " | "},
                    new String[] {
                    "   ",
                    "--g",
                    " rJ"},
                    new String[] {
                    "rg ",
                    "J| ",
                    " | "},
                    new String[] {
                    "   ",
                    "g  ",
                    "Lg "});
        } else if (c == 'L') {
            return Arrays.asList(
                    new String[] {
                    "rJ ",
                    "| r",
                    "L-J"},
                    new String[] {
                    " | ",
                    " L-",
                    "   "},
                    new String[] {
                    "rJ ",
                    "L--",
                    "   "},
                    new String[] {
                    " | ",
                    " |r",
                    " LJ"},
                    new String[] {
                    " Lg",
                    "  L",
                    "   "});
        } else if (c == 'J') {
            return Arrays.asList(
                    new String[] {
                    " Lg",
                    "g |",
                    "L-J"},
                    new String[] {
                    " | ",
                    "-J ",
                    "   "},
                    new String[] {
                    " Lg",
                    "--J",
                    "   "},
                    new String[] {
                    " | ",
                    "g| ",
                    "LJ "},
                    new String[] {
                    "rJ ",
                    "J  ",
                    "   "});
        } else if (c == '-') {
            return Arrays.asList(
                    new String[] {
                    " rg",
                    "g|L",
                    "LJ "},
                    new String[] {
                    "rg ",
                    "J|r",
                    " LJ"},
                    new String[] {
                    "   ",
                    "---",
                    "   "},
                    new String[] {
                    "r-g",
                    "J L",
                    "   "},
                    new String[] {
                    "   ",
                    "g r",
                    "L-J"},
                    new String[] {
                    "rg ",
                    "JL-",
                    "   "},
                    new String[] {
                    " rg",
                    "-JL",
                    "   "},                 
                    new String[] {
                    "   ",
                    "gr-",
                    "LJ "},
                    new String[] {
                    "   ",
                    "-gr",
                    " LJ"}                                      
                    );                      
        } else if (c == '|') {
            return Arrays.asList(
                    new String[] {
                    " Lg",
                    "r-J",
                    "Lg "},
                    new String[] {
                    "rJ ",
                    "L-g",
                    " rJ"},
                    new String[] {
                    " | ",
                    " | ",
                    " | "},
                    new String[] {
                    " Lg",
                    "  |",
                    " rJ"},
                    new String[] {
                    "rJ ",
                    "|  ",
                    "Lg "},
                    new String[] {
                    " Lg",
                    " rJ",
                    " | "},
                    new String[] {
                    " | ",
                    " Lg",
                    " rJ"},
                    new String[] {
                    "rJ ",
                    "Lg ",
                    " | "},
                    new String[] {
                    " | ",
                    "rJ ",
                    "Lg "}                  
                    );
        } else {
            List<String []> ret = new ArrayList<String []>();
            ret.add(
                    new String[] {
                    "   ",
                    "   ",
                    "   "});
            return ret;
        }

    }
}

2
นี่เป็นหนึ่งในโซลูชั่นที่ล้ำสมัยที่สุด! +1 สำหรับแบทแมน =)
ข้อผิดพลาด

ฉันรักอันนี้
trichoplax
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.