1

Тема: Стародавні артефакти забутих цивілізацій при повороті пікселів

Є картинка, і ще одна картинка, розміри котрої можуть змінюватись, але то не головне.
Головне те, що друга картинка повинна накладатись на першу, піксель на піксель.
Коли перша картинка повернута на 0, 90, 180 або 360 градусів, то все окей, тому що піксельні решітки накладаються одна на одну гарненько, піксель на піксель.
Але якщо повернути якусь з картинок на, наприклад, 45 градусів, то вже виникають дефекти.
ось такі от
http://replace.org.ua/extensions/om_images/img/593c0337354ec/tBvL4fspTeKBpWJd8AgIuw.png

Тут я змінюю пікселі першої картинки за допомогою матриці повороту. І робиться щось не те, чого б хотілось.
Єдиний плюс, це те, що різне значення повороту видає різний візерунок
http://replace.org.ua/extensions/om_images/img/593c0337354ec/kvDu8n01QT6KKZ5KFm90hA.png

То хто зна, що там відбувається, і як то пофіксити?

Озьдо код

public static void Process2(ref Texture2D texture, Texture2D boomTexture, Vector2 textureOrigin, Vector2 pointOfImpact, float angle, float radius, bool test=false)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        pointOfImpact = WorldToPixel(texture.width, texture.height, pointOfImpact, textureOrigin, angle);

        Vector4[] boundingBox = CalculateBoundingBox(pointOfImpact, radius, texture.width, texture.height, boomTexture.width, boomTexture.height);

        float stepX = (boundingBox[1].y - boundingBox[1].x) / (boundingBox[0].y - boundingBox[0].x);
        float stepY = (boundingBox[1].w - boundingBox[1].z) / (boundingBox[0].w - boundingBox[0].z);

        Debug.Log("step x: " + stepX + "  step y: " + stepY);
        Debug.Log("main tex box: "+boundingBox[0]+"  helping tex box: "+boundingBox[1]);

        float x0 = boundingBox[1].x, y0=0;

        float centerX = (boundingBox[0].y - boundingBox[0].x) / 2f + boundingBox[0].x;
        float centerY = (boundingBox[0].w - boundingBox[0].z) / 2f + boundingBox[0].z;

        Debug.Log("center x: "+centerX+"  center y: "+centerY);

        for (int x = (int)boundingBox[0].x; x < boundingBox[0].y; x++)
        {
            y0 = boundingBox[1].z;

            for (int y = (int)boundingBox[0].z; y < (int)boundingBox[0].w; y++)
            {
                float tempX = x - centerX;
                float tempY = y - centerY;

                int newX = (int)(tempX * Mathf.Cos(angle * Mathf.Deg2Rad) - tempY * Mathf.Sin(angle * Mathf.Deg2Rad) + centerX);
                int newY = (int)(tempX * Mathf.Sin(angle * Mathf.Deg2Rad) + tempY * Mathf.Cos(angle * Mathf.Deg2Rad) + centerY);

                if(test)
                Debug.Log("old x: "+x+"  old y: "+y+"  new x: "+newX+"  new y: "+newY);

                if(newX<0 || newX > texture.width || newY<0 || newY > texture.height)
                    continue;

                Color color = texture.GetPixel(newX, newY);
                color.g = 1;
                texture.SetPixel(newX, newY, color);

                y0 += stepY;

                continue;
                texture.SetPixel(newX, newY, color);
                if (color.a == 0)
                    continue;

                color.g = 1;
                color.a = 1-boomTexture.GetPixel(Mathf.FloorToInt(x0), Mathf.FloorToInt(y0)).a;
                texture.SetPixel(newX, newY, color);

            }

            x0 += stepX;
        }

        var res = stopwatch.ElapsedTicks;
        stopwatch.Stop();
        Debug.Log("it took " + res + " ticks,  x0: "+x0+" y0: "+y0);
    }
All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

2

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

Відбувається биття (просторових) частот, воно ж муар.
Пофіксити — наскільки я розумію, лише зменшивши внесок(які ж вони страшні, ці статті) високих просторових частот. Або заблурити картинки і втратити деталі (нічому буде бити), або інтерполювати проміжні значення.

printf("Nested comments is %s\n", */*/**/"*/"/*"/**/ == '*' ? "OFF" : "ON");
Подякували: NagarD, 0xDADA11C7, 221VOLT, LoganRoss, leofun015

3

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

та яке биття, то просто округлення до інта таке погане
озьдо
https://www.desmos.com/calculator/qkv976yyka
там, коли тягаєте повзунки x1 та y1, то видно, що спочатку видно перший піксель в стрічці, потім воно один раз скаче на іншу позицію, після чого залишається на цій позиції, і потім ще раз скаче. А мало б три рази підряд скакати.
От там і створюються прогалини, чи якось так.

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

4

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

озьдо я ще зобразив, як виглядають повернуті пікселі, якщо не округлювати нічого.
Місця обведені червоними рисочками - пікселі
http://replace.org.ua/extensions/om_images/img/593c0328b2367/jy2ODBYBTrmE1NV19IUfpA.png

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

5 Востаннє редагувалося ReAl (10.06.2017 22:50:06)

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

FakiNyan написав:

та яке биття,

Звичайне, як між струнами неналаштованої гітари. Коли вища затиснена і нижча незатиснена мають звучати в унісон, а маємо НЧ «вау-вау-вау-вау»

FakiNyan написав:

А мало б три рази підряд скакати.

Може я щось не зрозумів? Які три?
Візьмемо картинку.
Неповернуті пікселі (які червоним відбиті): від другого знизу пікселя у лівій колонці вгору вправо під 45° маємо на три неповернутих пікселя приблизно чотири повернутих. Просторова частота по діагоналі неповернутих пікселів у sqrt(2) нижча і все. Один потрапляє переважно на чорний, наступний не на білий, а на суміш 50:50, наступний переважно на білий.
Як не округлюй, а на приблизно три переходи між чотирма повернутими пікселями маємо два переходи між трьома неповернутими.

А округлення координати до цілих споріднене до шуму квантування по амплітуді, має свій спектр, який накладається на биття (або можна сказати, що биття модулює спектр шуму квантування). Ото, залежно від кута повороту/співвідношення частот, воно і вилазить.

«Давненько я не брав у руки шашок»™, але просто «пофіксити взагалі» це неможливо, треба залежно від задачі щось витягувати за рахунок того, що щось убиватиприм1.
Треба, щоб людський мозок зі своїм продовженням-оком не відволікалися на ці артефакти і могли побачити протяжні малоконтрастні об'єкти (гм… некрасиві такі плямочки на флюорографії, наприклад) — слід задавити ці високі частоти перед поворотами/масштабуваннями/корекціями геометричних спотворень.
Для витягування контрастних дрібних деталей з ігноруванням повільних градієнтів треба щось інше придумувати (мабуть, спочатку upsampling/інтерполювання, щоб посунути ці дрібні деталі у середню частину спектру, крутити/гратися, потім downsampling, інакше з пари десятків пікселів дрібної деталі нічого не залишиться, биттям+шумом заб'є).


прим1Тобто ганяти туди-сюди по спектру енергію, яка, кажуть, не пропадає. Взагалі  у мене є думка, що noise shaping у вигляді різних правил безпеки витісняє дрібні аварії і травми за межі «смуги пропускання» повсякденного життя, але енергія збирається за межами смуги і періодично вихлюпується великими катастрофами з купою жертв. Але це вже якась метафізика–метаматематика.

printf("Nested comments is %s\n", */*/**/"*/"/*"/**/ == '*' ? "OFF" : "ON");
Подякували: 0xDADA11C7, FakiNyan, leofun013

6

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

от срака, ніфіга не пойняв

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

7

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

Треба, щоб людський мозок зі своїм продовженням-оком не відволікалися на ці артефакти і могли побачити протяжні малоконтрастні об'єкти

так мені не для людського ока треба. Мені треба вирізати з однієї картинки частинку, форму котрої визначають непрозорі пікселі іншої картинки.
Потрібне це для того, аби симулювати знищення частинки якогось об'єкту вибуховою хвилею кулі. (це все в 2D)
Якби паттерн вибухової хвилі був простим кільцем, то тут як не крути, все буде окей. А от з картинками вже проблема.
Так от, після знищення частинки об'єкту мені ще треба буде перебудовувати колайдери, тому важливо, аби воно не просто виглядало так, наче від об'єкту відкусили шматочок, але й насправді було таким, на рівні пікселів.

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

8

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

надибав от таку статтюйку
http://datagenetics.com/blog/august32013/

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...
Подякували: LoganRoss1

9 Востаннє редагувалося FakiNyan (11.06.2017 22:32:03)

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

певно, я щось роблю не так, тому що досі маю дірочкиhttp://replace.org.ua/extensions/om_images/img/593d9ad24dad7/XB1uDNs_RCKugmADD9yRxQ.png

озьдо весь код

    def fillPixels(self):

        step = math.floor(512/self.width)

        self.clearImage()

        print("angle: {0}".format(self.angle))

        count=0

        a = [[1, -math.tan(self.angle/2)], [0, 1]]
        b = [[1, 0], [math.sin(self.angle), 1]]

        for x in range(0, self.width):
            for y in range(0, self.height):

                tempX = x - (self.width)/2.0
                tempY = y - (self.width)/2.0

                xy = [tempX, tempY]

                fnewXY = np.matmul(np.matmul( np.matmul(a, b), a), xy)

                newXY = [fnewXY[0]+(self.width)/2.0, fnewXY[1]+(self.width)/2.0]

                if newXY[0] <0 or newXY[0]> self.width-1 or newXY[1] < 0 or newXY[1] > self.height-1:
                    continue

                self.fillPixel(round(newXY[0]), round(newXY[1]))
All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

10

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

надибав алгоритма простішого і очевиднішого

на основі розмірів зображення та повороту будуємо bounding box для майбутнього зображення (котре вже повернуте), після чого проходимось по всім пікселям цього bounding box'а, та виконуємо поворот пікселів на той самий кут, але негативний.
Ну, тобто, якщо треба повернути на 45 градусів, то ми повертаємо на -45. Після чого перевіряємо, чи отриманий піксель знаходиться в bounding box оригінального зображення. Якщо знаходиться, то малюємо його по координатам поточного пікселя в повернутому bounding box'і.

Тобто, ми можемо записати один й той самий піксель з оригінального зображення в два різних пікселі нового, повернутого зображення, але це дає гарантію, що в нас не буде дірок.

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

11

Re: Стародавні артефакти забутих цивілізацій при повороті пікселів

озьдо кід, я його трохи почистив

import sys
import random
import time
import numpy as np
import math
from PyQt4.QtGui import *
from PyQt4.QtCore import pyqtSlot

class Example(QWidget):

    def __init__(self):
        super(Example, self).__init__()
        self.lbl=QLabel()
        self.image = QImage(512, 512, QImage.Format_RGB32)
        self.hbox = QHBoxLayout()
        self.pixmap = QPixmap()
        self.angle = 0
        self.width=32
        self.height=32
        self.offsetX=8
        self.offsetY=8

        self.initUI()
        
    def mousePressEvent(self, QMouseEvent):
        self.angle = self.angle + (math.pi/12)
        self.fillPixels()

    def initUI(self):    
        self.hbox = QHBoxLayout(self)
        self.hbox.setContentsMargins(0,0,0,0)
        self.pixmap = QPixmap()
        self.move(300, 200)

        self.addedWidget = None

        self.setLayout(self.hbox)

        self.fillPixels()

        self.show()  


    def fillPixel(self, x, y):
        realX = x*(512/self.width)
        realY = y*(512/self.height)

        for x1 in range(math.floor(realX), math.floor(realX+(512/self.width))):
            for y1 in range(math.floor(realY), math.floor(realY+(512/self.height))):

                if (x+y)%2==0:
                    color = 200
                else:
                    color=70
                
                self.image.setPixel(x1, y1, qRgb(color, color, color))


    def clearImage(self):
        for x in range(0, 512):
            for y in range(0, 512):
                self.image.setPixel(x, y, qRgb(0, 0, 0))

    def fillPixels(self):

        step = math.floor(512/self.width)

        self.clearImage()

        print("angle: {0}".format(self.angle))

        rotM = [[math.cos(-self.angle), -math.sin(-self.angle)], [math.sin(-self.angle), math.cos(-self.angle)]]

        realWidth = round((self.width-self.offsetX*2)* abs( math.cos(self.angle) )+ (self.height-self.offsetY*2)* abs( math.cos(math.pi/2.0 - self.angle) ))
        realHeight = round((self.width-self.offsetX*2)* abs( math.sin(self.angle) ) + (self.height-self.offsetY*2)* abs( math.sin(math.pi/2.0 - self.angle) ))

        realStartX = round((self.width-realWidth)/2)
        realStartY = round((self.height-realHeight)/2)

        print("width: {0}  height: {1}  start at: {2};{3}".format(realWidth, realHeight, realStartX, realStartY))

        if realStartX < 0:
            realStartX=0
        if realStartY < 0:
            realStartY=0

        for x in range(realStartX, realWidth+realStartX):
            for y in range(realStartY, realHeight+realStartY):

                tempX = x - (self.width)/2.0
                tempY = y - (self.width)/2.0

                fnewXY = [tempX, tempY]

                fnewXY = np.matmul(rotM, fnewXY)

                newXY = [fnewXY[0]+(self.width)/2.0, fnewXY[1]+(self.width)/2.0]

                if newXY[0] <0 or newXY[0]> self.width-1 or newXY[1] < 0 or newXY[1] > self.height-1 or (newXY[0] <self.offsetX and abs(newXY[0]-self.offsetX) > 0.000001) or newXY[0] >self.width-self.offsetX or newXY[1] <self.offsetY or newXY[1] >self.height-self.offsetY:
                    #print("skip  x: {0}  y: {1}  with data  newXY[0]: {2}  newXY[1]: {3}".format(x, y, newXY[0], newXY[1]))
                    continue

                self.fillPixel(x, y)

        self.pixmap = self.pixmap.fromImage(self.image)

        if self.lbl == None:
            self.lbl = QLabel(self)

        else:
            self.lbl.setPixmap(self.pixmap)

        if self.addedWidget == None:
            self.hbox.addWidget(self.lbl)
            self.addedWidget = True

        self.repaint()
        self.update()


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

І декілька результатів

Прихований текст
https://image.prntscr.com/image/FrOm_FwwQ-CrCf8dRflM3A.png
https://image.prntscr.com/image/bunlkP4rTdC-X8dqAyRkYg.png
https://image.prntscr.com/image/zttlpaDERlqaVpJj_fvUsg.pnghttps://image.prntscr.com/image/8xFGCD3pRxWQsY8fIz9S9A.png

там порівняння флоатів не повністю допиляно, але то фігня, якщо комусь тре, то сам зможе то легко зробити

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...