Skip to content

Commit

Permalink
RomAlignerTilting implementing a rotation matrix. #120
Browse files Browse the repository at this point in the history
  • Loading branch information
travisgoodspeed committed Aug 16, 2024
1 parent 2dc78c6 commit e1c81fb
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 2 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ set(MRT_SOURCES
# Alignment stategies
romaligner.h romaligner.cpp
romalignerreliable.h romalignerreliable.cpp
romalignertilting.h romalignertilting.cpp
romaligndialog.h romaligndialog.cpp romaligndialog.ui
romrule.h romrule.cpp
romrulecount.h romrulecount.cpp
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ now sets the home position. Zooming and movement keys now work in the
second view. Perfectly duplicate lines are now culled during DRC by
the `V` key. Rows and columns are now stored as sorted lists intead
of sets. Rows and columns are now in a consistent order in the file
export. Performance boosts in bit marking, background bit marking
and alignment. Universal binary for macOS.
export. Performance boosts in bit marking, background bit marking and
alignment. Universal binary for macOS. RomAlignerTilting works
better for designs with a gap between banks.

2024-07-14 -- Fixes crash when deleting a double-selected item. Delete
and backspace now delete objects like `D`. Multiple disassemblers.
Expand Down
2 changes: 2 additions & 0 deletions maskromtool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

//#include "romalignernew.h" //Deprecated May 2024.
#include "romalignerreliable.h"
#include "romalignertilting.h"
#include "rombitsamplerwide.h"
#include "rombitsamplertall.h"

Expand Down Expand Up @@ -91,6 +92,7 @@ MaskRomTool::MaskRomTool(QWidget *parent, bool opengl)

//Strategies should be initialized.
addAligner(new RomAlignerReliable()); //First is default.
addAligner(new RomAlignerTilting());
addSampler(new RomBitSampler()); //First is default.
addSampler(new RomBitSamplerWide());
addSampler(new RomBitSamplerTall());
Expand Down
178 changes: 178 additions & 0 deletions romalignertilting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "romalignertilting.h"
#include "maskromtool.h"


/* All comparisons and sorts are performed on tilted bit positions.
* This tilt is based upon the angle of the top row in the previous
* alignment, and that angle is not saved between runs of the program.
*
* Aside from the QTransform mapping, this is the same as RomAlignerReliable.
*/
static QTransform transform;
static bool leftOf(RomBitItem * left, RomBitItem * right){
auto l=transform.map(left->pos());
auto r=transform.map(right->pos());
return l.x() < r.x();
}
static bool above(RomBitItem * top, RomBitItem * bottom){
auto t=transform.map(top->pos());
auto b=transform.map(bottom->pos());
return t.y() < b.y();
}

RomAlignerTilting::RomAlignerTilting() {
name="RomAlignerTilting";
transform=QTransform();
}

qreal RomAlignerTilting::angle(){
RomBitItem *first, *last;

//Fail out with zero degrees.
if(rowstarts.empty())
return 0.0;

first=rowstarts[0];
last=first;
while(last->nexttoright)
last=last->nexttoright;

QPointF left=first->pos(), right=last->pos();
QLineF line(left, right);

lastangle=line.angle();
transform=QTransform().rotate(lastangle);
angleset=true;

/* How well did the transform work?
auto l=transform.map(left), r=transform.map(right);
auto newangle=QLineF(l,r).angle();
qDebug()<<"Post transform, angle is "<<newangle; //Very small, we hope.
*/

return lastangle;
}


RomBitItem* RomAlignerTilting::markBitTable(MaskRomTool* mrt){
QList<RomBitItem *> bits=mrt->bits;

//The very first time we align this, we continuously update the transform.
bool firstrun=!angleset;

//We recalculate the transformation whenever this grows by 16.
int bitcount=0;

//First we remove all the old bit marks and pointers.
foreach (RomBitItem* bit, bits){
//Clear the markup details.
bit->marked=false; //Not used by this aligner.
bit->nextrow=0;
bit->nexttoright=0;
bit->lastinrow=0; //Speeds up linked list usage.
bit->row=-1;
bit->col=-1;
}
rowstarts.clear();
leftsorted.clear();

// We'll need presorted collections by X and Y.
for(RomBitItem *bit: bits){
leftsorted<<bit;
}
//We read each row from the left. Presorting doesn't help determinism.
std::sort(leftsorted.begin(), leftsorted.end(), leftOf);

//Each bit is either its own row or the next in an existing row.
for(RomBitItem *bit: leftsorted){
RomBitItem* nearest=nearestBit(bit);
if(!nearest){ //First bit is its own row.
rowstarts<<bit;
}else{
auto nearestpos=transform.map(nearest->pos());
auto bitpos=transform.map(bit->pos());
qreal dx=qFabs(nearestpos.x()-bitpos.x());
qreal dy=qFabs(nearestpos.y()-bitpos.y());
if(dx<dy){ //New row because Y distance is greater.
rowstarts<<bit;
}else{ //Existing row because X distance is greater.
nearest->nexttoright=bit;
if(firstrun) angle();
}
}
}

return linkresults();
}


//Nearest bit from existing rows.
RomBitItem* RomAlignerTilting::nearestBit(RomBitItem *item){
/* This returns the best match, but it does not guarantee that it
* is a good match. If the best match is further in Y than in
* X, you should start a new row.
*/

RomBitItem* nearest=0;
qreal nearestdy=1000.0;
auto itempos=transform.map(item->pos());

for(RomBitItem* bit: rowstarts){
/* Rather than chase the entire linked list, we try to keep the frist
* bit of each row pointing to either the end or some item near the end.
*
* We compare the mapped positions, not the raw positions.
*/
RomBitItem* startbit=bit;
if(bit->lastinrow)
bit=bit->lastinrow;
while(bit->nexttoright) bit=bit->nexttoright;
startbit->lastinrow=bit;

auto bitpos=transform.map(bit->pos());
qreal dy=qFabs(itempos.y()-bitpos.y());
if(dy<nearestdy){
nearestdy=dy;
nearest=bit;
}
}

return nearest;
}


//This updates the linked lists that MRT uses internally.
RomBitItem* RomAlignerTilting::linkresults(){
//Having assembled rows, we now need to sort them and return the top left.
std::sort(rowstarts.begin(), rowstarts.end(), above);

//Apply the linked list.
RomBitItem* lastbit=0;
for(RomBitItem *bit: rowstarts){
if(lastbit)
lastbit->nextrow=bit;
lastbit=bit;
}

//Number the bits by row and column.
int expectedcols=0;
int row=0;
for(RomBitItem* bit: rowstarts){
int col=0;
while(bit){
bit->col=col;
bit->row=row;
bit=bit->nexttoright;
col++;
}
row++;
}

//Do I want to correct by a tilt or by a shear?
qDebug()<<"Tilt angle of the first row after alignment is "<<angle();

if(row)
return rowstarts[0];

return 0;
}
40 changes: 40 additions & 0 deletions romalignertilting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef ROMALIGNERTILTING_H
#define ROMALIGNERTILTING_H

/* This is a fork of RomAlignerReliable that also measures
* the tilt of the lines and corrects for that tilt to better
* support images with a gap in the middle. It works by calculating
* a "tilt" of the image on each pass, then using the Reliable algorithm
* on the tilted bit positions.
*
* It isn't well tested yet, so if you do have need for it, consider
* pre-rotating your image. Gaps inside rows (between columns)
* will trigger the problem with RomAlignerReliable, but
* gaps inside columns (between rows) will not.
*
* https://github.com/travisgoodspeed/maskromtool/issues/120
*/

#include "romaligner.h"

#include<QTransform>

class RomAlignerTilting : public RomAligner
{
public:
RomAlignerTilting();
RomBitItem* markBitTable(MaskRomTool* mrt);
private:
QList<RomBitItem *> rowstarts; //All left-most bits of a row.
QList<RomBitItem *> leftsorted; //All bits sorted from left.

//Nearest bit from existing rows.
RomBitItem* nearestBit(RomBitItem *item);
RomBitItem* linkresults();
//QTransform transform;
qreal angle();
qreal lastangle=0;
bool angleset=false;
};

#endif // ROMALIGNERTILTING_H

0 comments on commit e1c81fb

Please sign in to comment.