Artykuły

    Kurs Assemblera cz. 7

    Metody kompresji danych

    Informacje tu przedstawione zostały zebrane z różnych plików RFC, opisów formatów walających się po sieci, własnych przemyśleń i doświadczeń zdobytych w czasie pisania kompresora plików RIP. Kompresor jest na PC, depaker na Atari, żeby była jasność. Nie namawiam Was żebście napisali np. RAR-a na Atari :)

    Może przydadzą się te informacje jakiemuś ambitnemu człowiekowi :), który potrafi coś zrobić ze zdobytą wiedzą (baloisa należałoby wykluczyć).

    1. Huffman coding (Squezzing).

    Kompresja ta polega na zaprezentowaniu najczęściej występujacych danych na mniejszej liczbie bitów. Znajduje najoptymalniejszą kombinację bitów, jest lepszy od kodowania Shannon Fano. Stosowanie tego algorytmu ma sens tylko wtedy gdy częstotliwość występowania elementów jest w stosunku 1/2. Gdy w ciągu będzie 256 różnych elementów to najdłuższy kod Huffmana bedzie miał 16 bitów.

    Zakładamy, że na początku wszystkie 256 elementów występują po 1 razie. Kody tworzymy dla wszystkich 256 elementów. Czytamy cały ciąg danych, zliczając poszczególne elementy. Następnie wybieramy 128 (połowa występujących elementów) największych wartości i sumujemy. Jeśli stosunek tej sumy do liczby wszystkich elementów jest większy od 0.5 to kompresja sie opłaca.

    Liczbę bitów potrzebnych do zakodowania danej wartości można obliczyć ze wzoru:

     L=-log2(P(A))     gdzie P(A) - częstotliwość wystąpienia wartości A
                               L  - szukana liczba bitów
    

    1a. Huffman Codes. Obliczanie kodów Huffmana

    • Liczymy elementy danego ciągu.
    • Bierzemy dwie najmniejsze wartości reprezentujące liczbę elementów. Pierwsza wartość będzie binarnym 0, druga binarnym 1. Zaznaczamy wybrane elementy, by później traktować je jako jedność (suma).
    • Powtarzamy pkt.1 dopóki nie połączymy wszystkich par.
    • Rewersujemy kombinacje bitów.
      np. A A A A B C A D B E C B
      A-5, B-3, C-2, D-1, E-1
            |   |      | Reverse |                         0 / \ 1
    --------+---+------+---------+                         /     \
     3      | A | 0    | 0       |                       /     0 / \ 1
     3 2    | B | 01   | 10      |                     /       /     \
     3 2 1  | C | 011  | 110     |                   /       /     0 / \ 1
     3 2 1 0| D | 0111 | 1110    |                 /       /       /     \
     3 2 1 0| E | 1111 | 1111    |               /       /       /     0 / \ 1
    --------+---+------+---------+              A       B       C        D E
    12 7 4 2|

    Do pliku wyjściowego należy przesłać informację o rozkładzie elementów, co spowoduje zmniejszenie stopnia kompresji. Sprytne upakowanie tej informacji nie wpłynie znacznie na końcowy wynik kompresji np.

    1) 256 kodów Huffmana i 256 wartości z zakresu 0-15 (4 bity) określajacych
       długość kodów+1 np. 0000 0  0001 10  0010 110  0011 1110  0011 1111
       
       0000 0    oznacza kod o długości 0000+1 bitów, w naszym w/w przykładzie tym bitem jest 0
       0001 10   oznacza kod o długości 0001+1 bitów = 2 bity, w naszym przykładzie to 10
       ...
       0011 1110 oznacza kod o długości 0011+1 bitów = 4, w naszym przykładzie 1110
       
    2) 256 kodów Huffmana i bajty w których bity 4-7 oznaczają liczbę kodów+1, a bity 0-3 długość kodów+1 np. 00 01 12 13 a6
       
       00    czyli 1 kod o długości 1 bita
       12    czyli 2 kody o długości 3 bitów
       a6    czyli 11 kodów o długości 7 bitów
    

    Można spakować kody obiema w/w metodami i wybrać najlepszą zaznaczając to w pliku wyjściowym.

    1b. Dekompresja kodów Huffmana

    BitLenght = 0
    loop
     read(bit)
      Byte(BitLenght) = bit : BitLenght=BitLenght + 1
      Value = 257
      Count = 0
       loop while ( Value = 257 )
          if (LenghtCode(Count) = BitLenght) and (Code(Count) = Byte(0..7)) then
             Value = Count
          endif
         Count = Count + 1
       end loop
    
    end loop
    

    Aby zdekodowac kod, musimy go czytac bit po bicie i porownywac z tablicami gotowych kodow i ich dlugosciami. Przy tym sposobie dekompresja bedzie szybsza jesli tablice beda posortowane wg. rosnacych wartosci 'dlugosci kodow'. Najkrotsze kody zostana zdekodowane najszybciej.

    1c. Dynamic Huffman Codes.

    Ta metoda eliminuje konieczność przeczytania wszystkich elementów i przesłania kodów do wyjścia, jednak proces dekompresji jest wolniejszy w porównaniu do klasycznego sposobu.

    Na początku liczbę poszczególnych 256 elementów ustawiamy na 1. Czytamy element i odpowiednio modyfikujemy drzewo binarne.

    Dekompresja w tym przypadku przebiega podobnie jak kompresja.

    2. Shannon-Fano coding.

    Zasada tego kodowania jest taka sama jak Huffmana. Zastąpić symbole krótszymi symbolami, tyle że tutaj podajemy jakiej długości mają być te kody. Zostają stworzone kody o zadanej długości.

    1) Obliczamy na ilu bitach reprezentowany jest dany symbol (Bit Lenght).
    2) Sortujemy wartości Bit Lenght (ciąg rosnący).
    3) Generujemy kody:
    
        Code = 0
        CodeIncrement = 0
        LastBitLength = 0
        i = numer kodu Shannon-Fano - 1
    
        loop while (i >= 0)
            Code = Code + CodeIncrement
              if BitLength(i) <> LastBitLength then
                LastBitLength=BitLength(i)
                CodeIncrement = 1 shifted left (16 - LastBitLength)
              endif
          ShannonCode(i) = Code
          i = i - 1
        end loop
    
    4) Dokonujemy rewersji bitów poszczególnych kodów.
    5) Przywracamy stare uporządkowanie wartości Bit Lenght.
    
    np. Bit Lenght = 3, 3, 3, 3, 3, 2, 4, 4   (kody od 0-7)
    
                                      Reversed     Order     Original
    Val  Sorted   Constructed Code      Value     Restored    Length
    ---  ------   -----------------   --------    --------    ------
    0:     2      1100000000000000        11       101          3
    1:     3      1010000000000000       101       001          3
    2:     3      1000000000000000       001       110          3
    3:     3      0110000000000000       110       010          3
    4:     3      0100000000000000       010       100          3
    5:     3      0010000000000000       100        11          2
    6:     4      0001000000000000      1000      1000          4
    7:     4      0000000000000000      0000      0000          4
    

    3. LZW78 (Lempel-Ziv-Welch).

    Algorytm ten stosowany jest w m.in. w kompresji GIF'a. Jest to kompresja używająca słownika. Kody generowane są z zakresu od 9-13 bitów, w przypadku GIF'a 9-12.

    Symbole reprezentowane są przez wartości z zakresu 0-255, kody słownika 256-... Jeśli zabraknie bitów do zaprezentowania kodu słownika liczba bitów jest zwiększana o 1, aż do pewnej ustalonej granicy (12 lub 13), wtedy generowany jest kod 256 - Clear Dictionary (czyść słownik), kod 257 - End Data (Koniec).

    np. A B C C B D D D C A B EOF
    
       Input   | Output  |  Dictionary
    -----------+---------+--------------
    AB         |    A    |  258=(A,B)
    BC         |    B    |  259=(B,C)
    CC         |    C    |  260=(C,C)
    CB         |    C    |  261=(C,B)
    BD         |    B    |  262=(B,D)
    DD         |    D    |  263=(D,D)
    DD 263,C   |    263  |  264=(263,C)
    CA         |    C    |  265=(C,A)
    AB 258,EOF |    258  |
    

    4. Arithmetic coding.

    Metoda ta opiera się podobnie jak Huffman czy ShanonFano o rozkład prawdopodobieństwa. Z ciągu wejściowego pobieramy ciąg, w którym występują max 2 symbole. Dla tych 2 symboli szukamy 1-nej kombinacji bitów X (ciąg 2 symboli zakodujemy jedną kombinacją bitów), którą znajdujemy dzieląc przedział <0,1) na 2 części (górną lub dolną), równe częstotliwości występowania tych elementów. Przedział zawężamy od góry lub od dołu tak długo, aż nie przeczytamy wszystkich elementów.

    4a. Ograniczanie przedziału.

    np. (27/64,36/64) - ograniczyć o 3/4 od góry
    
            27/64            36/64
              +----------+-----+
                   3/4     1/4
    

    czyli to jest 3/4 obszaru (36/64 - 27/64) = 9/64 * 3/4 = 27/256, ten wynik dodajemy do dolnej granicy (lewej), czyli (27/64 + 27/256) = 135/256 nowy obszar to (27/64,135/256) np. (0,9/16) - ograniczyć 1/4 od dołu

              0               9/16
              +----------+-----+
                   3/4     1/4
    

    podobnie jak poprzednio jest to 1/4 obszaru (0 - 9/16) = 9/16 * 1/4 = 9/64, ten wynik odejmujemy od górnej granicy (prawej), czyli (9/16 - 9/64) = 27/64 nowy obszar to (27/64,9/16)

    4b. Reprezentacja binarna ułamka.

    Mnożymy licznik *2, jeżeli jest większy od mianownika to zapisujemy binarne 1 i odejmujemy od licznika mianownik, w przeciwnym wypadku zapisujemy binarne 0 (licznik < mianownika). Kończymy gdy licznik = mianownik.

    np.
            27/64 |                 135/256 |
            54/64 | 0     135/256*2=270/256 | 1
            108/64| 1       14/256*2=28/256 | 0
    44/64*2=88/64 | 1                56/256 | 0
    24/64*2=48/64 | 0               112/256 | 0
            96/64 | 1               224/256 | 0
    32/64*2=64/64 | 1     224/256*2=448/256 | 1
                          192/256*2=384/256 | 1
                          128/256*2=256/256 | 1
    
    27/64 = 0.011011      135/256 = 0.10000111
    

    4c. Przykład kompresji.

    np. AABACAD wybieramy AABA zliczamy elementy A-3 B-1, razem 4, czyli A=3/4, B=1/4 - na początku nasza szukana wartość X jest w przedziale <0,1), przedział ten dzielimy na 2 części 3/4 i 1/4 (musimy z góry przyjąć jak będziemy dzielić nasz przedział, zawsze tak samo).

                   0<= X <1
    
              0                1
              +----------+-----+
                   3/4     1/4
    

    bierzemy pierwszy symbol - A, ograniczamy przedział od góry do 3/4, nasz nowy przedzial to: 0<= X <3/4 i dzielimy go na 2 części 3/4 i 1/4, następny symbol to znowu - A, przedział <0,3/4) ograniczamy o 3/4, czyli 9/16 (((3/4 - 0) * 3/4) + 0) = 9/16 ), nowy przedział to <0,9/16), następny symbol to B - tym razem wybieramy dolną część przedziału <0,9/16), czyli 1/4, nowy przedział to <27/64,9/16), ostatni symbol to A - przedział <27/64,9/16) ograniczamy o 3/4 od góry, nasz nowy, ostateczny przedział to (27/64,135/256).

    Ułamki musimy teraz zaprezentować binarnie, czyli

                    27/64  <= X < 135/256
                  0.011011 <= X < 0.10000111
    

    teraz wybieramy z powyzszego przedzialu liczbe, reprezentowana przez najmniejsza liczbe bitow, czyli 0.1 (0.01 ma wiecej bitow), odrzucamy 0. i otrzymujemy 1, a wiec 4 elementy AABA zakodujemy 1 bitem = 1.

    4d. Dekompresja.

    W spakowanym ciągu musi znaleźć się informacja o rozkładzie prawdopodobieństwa naszych elementów, czyli A=3/4, B=1/4. Bierzemy bit, jest nim 1, dokładamy 0. czyli 0.1, jest to binarne 1/2. 1/2 zawiera się w 3/4 pierwszego przedziału czyli szukanym znakiem jest A, obcinamy teraz przedział do 3/4 - <0,3/4). 1/2 ponownie mieści się w dolnej części tego przedziału czyli nasz znak to A. Przedział jeszcze raz obcinamy o 3/4 - <0,9/16), tym razem 1/2 mieści się w górnej części przedziału, dekodujemy więc B itd.

    5. RLE (Run Lenght Encode).

    Najprostsza z metod kompresji, aż dziw bierze że dopiero w tym miejscu się ona pojawia :) Sprawdza się w przypadku danych które nader często się powtarzają i występują "jeden za drugim".
    np.
    
                 AAAABBBCDDDDDDD
    
    po kompresji RLE dane powinny wyglądać tak:
    
                 4A 3B 1C 7D
    

    Czyli w skrócie, w ciągu wynikowym znajdą się pary: "liczba elementów" x "element"

    6. LZSS (Lempel-Ziv-Storer-Szymanski).

    Jeden z lepszych sposobów kompresji, jego różne kombinacje znalazły zastosowanie w Zip-ie jako deflating, lub w okrojonej formie w Cruncherach na 8-bitowce, albo w RIP-ie :). Są różne kombinacje tej metody, ale założenia są takie same.

    W ciągu wejściowym szukamy identycznych fraz. Po znalezieniu identycznej frazy, w ciągu wynikowym zapisujemy jej ofset i długośc, jeśli nie znaleziono powtarzającej się frazy wysyłamy jej fragment do pliku wynikowego. Np:

                 alamakota,akotmaale
    
    • Zakładamy, że szukamy 3 elementowych fraz. Pierwsza 3 elementowa fraza to "ala", przeszukujemy pozostały ciąg od znaku 4 do ostatniego, brak takiej samej frazy. Wysyłamy do pliku wynikowego 1 znak "a" (UNPACK).
    • Teraz bierzemy fraze "lam", od znaku 2 do 4, przeszukujemy od 5 do ostatniego, brak identycznej frazy. Wysyłamy do pliku wynikowego 1 znak "l".
    • Bierzemy fraze "ama", od znaku 3 do 5, przeszukujemy od 6 do ostatniego, brak powtarzających się fraz. Zapisujemy "a".
    • Bierzemy fraze "mak", od znaku 4 do 6, przeszukujemy od 7 do ostatniego, brak takich samych fraz. Zapisujemy "m".
    • Bierzemy fraze "ako", od znaku 5 do 7, przeszukujemy od 8 do ostatniego, znajdujemy na pozycji 11 fraze "ako". Teraz sprawdzamy maksymalną jej długość, tak długo dopóki znaki są identyczne z frazą porównywaną. Okazuje się, że nasza najdłuższa fraza to "akot". Zamiast 4 znaków zapiszemy OFSET, czyli odległość do frazy z którą porównywaliśmy 11-5=6, oraz długośc (MATCH_LEN) frazy, czyli 4.
    • Omijamy znalezioną frazę, bierzemy nową od znaku 10 do 12, przeszukujemy od 13 do ostatniego. Itd.....

    Nasz przykładowy ciąg był krótki, normalnie mamy do czynienia z dłuższymi ciągami, przekraczającymi zakres 1 bajtu 0..255, a pozatym ciągle mamy wartości 8bitowe: OFSET, MATCH_LEN, UNPACK. Te 3 rodzaje danych powinniśmy teraz zacząć pakować Huffmanem, albo inną probalistyczną metodą, wtedy dopiero uzyskamy zadowalającą kompresję.

    W kompresji RIP-a ofset jest z zakresu 0..255, minimalna długość frazy =3. Gdyby Fox wcześniej udostępnił swój DEFLATE.EXE, nie trzebaby było kombinować :)

    7. Kodowanie stratne. (by Rocky)

    Obraz może być kodowany kolumnami (z góry na dół) lub wierszami (z lewej do prawej). Zapisywana jest różnica pomiędzy poszczególnymi wartościami, za pomocą bitów.

    Jeśli poprzednia wartość jest mniejsza od aktualnej zapiszemy jako 0, w przeciwnym wypadku gdy większa lub równa jako 1. Wartość 8 bitową zapiszemy w ten sposób za pomocą 1 bitu.

    W przypadku gfx ATARI kodowanie to można zastosować w trybie 16 odcieni szarości 9 Basica. Przeglądamy gfx pionowymi kolumnami i zapisujemy różnicę np. na 1 bicie. 4 bity (pixel) zapiszemy na 1 bicie. Obraz zostanie rozmazany, straci na ostrości. W przypadku animacji nie powinno być to tak mocno zauważalne.

    Przykładem zastosowania tego typu kompresji jest 16kb intro "Vasco", w którym jest sporo grafik udających HIP-a.

    8. Zastosowanie. czyli luźne myśli i nie tylko :)

    HF - Huffman
    SF - Shannon-Fano
    AR - Arithmetic coding

    Power Packer (Amiga) pakuje długości ciągów za pomoca 2 bitów, i w zależności od potrzeb zwiększa tą liczbę np.

    0 - 00         3 - 1100        6 - 111100
    1 - 01         4 - 1101        7 - 111101
    2 - 10         5 - 1110        8 - 111110
    

    Gdy wystąpi kombinacja 11 tzn. że należy czytać dalej kod, długość ciągu jest zwiększana, powtarzamy operacje dopóki nie wystąpi kombinacja bitów różna od 11.

    W programie kompresującym trzeba zastosować obie metody HF i SF. HF służyć będzie do obliczenia długości kodów, a SF do ich bitowej reprezentacji. Jeśli nie zastosujemy SF musielibyśmy przesłać do pliku wynikowego całe drzewo HF (ok. 576*3 byte) zamiast 288 byte (1byte = 2nible, 288*2=576 byte), które są długościami kodów (1..15, 0 - brak kodu).

    Natomiast AR możnaby zastosować zamiast HF i SF. Ze względu jednak na skomplikowanie tej metody i dużą liczbę obliczeń nie jest ona tak popularna.

    Dekompresja w przypadku gdy czytamy bit po bicie i porównujemy czy przeczytany kod jest już naszym szukanym, trwa stosunkowo długo. Proces dekompresji powinien w jak najmniejszej liczbie kroków znaleźć szukany kod. Pewnym rozwiązaniem jest posortowanie wartości, ale najlepszym wydaje się zastosowanie drzewa. Drzewo pozwoli, że w każdym kroku jednoznacznie wyznaczymy właściwą drogę do szukanego kodu.

    Stosowanie tylko kompresji HF, SF lub AR nie jest zbyt opłacalne, stosuje się je tylko na końcu kompresji słownikowej. Pakuje się tymi 3-ema metodami kody słownika i pozostałe dane. Tak działają pakery ZIP, RAR, LHA itp. Niektóre z pakerów na samym początku starają się ocenić kompresowalność danych, jeśli żadna z kompresji nie daje pozytywnych rezultatów paker zapisuje dane jako STORE (bez kompresji).

    8a. Reprezentacja drzewa binarnego w pamięci komputera.

    Drzewo to tablica n-elementowa, w której znajdują się wartości szukane 0..255 (lub np. A..Z) i wartości 256..N, które oznaczają numer pary w drzewie.

             0   1
    256|    257|259|                A  010
    257|     F |258|                B  011
    258|     A | B |                C  100
    259|    260|261|                D  101
    260|     C | D |                E  110
    261|     E |262|                F   00
    262|     G | H |                G 1110
                                    H 1111
    
    Tworzenie drzewa:
     - zliczamy częstotliwości wszystkich elementów
     - bierzemy 2 najmniejsze elementy (kolejność znalezionych elementów gra rolę)
     - 1-wszy z elementów zachowujemy i zaznaczamy jako nową parę identyfikatorem
       >255, zaznaczamy nową wartość tej pary (suma)
       2-ugi element zaznaczamy jako usunięty, nie będziemy go brali pod uwagę
       przy następnym przeszukiwaniu
     - wstawiamy te 2 znalezione elementy do drzewa jako nową gałąź
    
    np.
    
      Frq                                     0   1
      
      A-1              0   / \   1    256|    A | C |  <- najniższa gałąź w drzewie
      B-2                /     \      257|   256| F |
      C-1              / \     / \    258|    B |257|
      D-3            /   / \   D E    259|    D | E |
      E-3          /   / \   \        260|   258|259|  <- wierzchołek drzewa
      F-1         B    A C    F
    
    	
    Dekompresja danych zapisanych na drzewie:
     - zaczynamy od znalezienia szukanego elementu X najniżej w drzewie
     - szukamy wyżej odwołania do gałęzi w której wystąpił nasz element
       powtarzamy obydwa pkt. aż do wierzchołka
     - otrzymane kombinacje bitów rewersujemy
    

    W naszym w/w przykładzie mamy drzewko z wierzchołkami od 256..260, teraz odczytamy przy jego pomocy jak wygląda binarna reprezentacja elementu A.

              0   1
    
      260|   258|259|  <- wierzchołek drzewa
      259|    D | E |
      258|    B |257|
      257|   256| F |
      256|    A | C |  <- najniższa gałąź w drzewie
    

    Chcemy zdekodować A. Znajdujemy ten element najniżej w drzewie, jest na pozycji (wierzchołku) 256, po lewej stronie czyli binarne 0. Odwołanie do pozycji 256 występuje na pozycji 257 i znajduje się po lewej stronie, czyli znowu 0. Odwołanie do 257 znajduje się pod 258 i jest po prawej stronie, czyli binarne 1. Odwołanie do 258 jest na pozycji 260, po lewej stronie (binarne 0) i jest to wierzchołek, więc kończymy nasze poszukiwania. Otrzymaliśmy 0010, rewersujemy i mamy 0100 i to jest binarny kod reprezentujący A, sprawdzcie na drzewku.

    Ok, to był przykład odczytania z drzewa reprezentowanego przez tablice, kodu reprezentującego konkretny element, ale w czasie dekompresji nie mamy podanej informacji jaki element będziemy właśnie rozpakowywać. Dekompresując czytamy bit po bicie i na tej podstawie dokonujemy wyboru jak poruszać się po drzewie, aż dojdziemy do elementu, który szukamy.

              0   1
    
      260|   258|259|  <- wierzchołek drzewa
      259|    D | E |
      258|    B |257|
      257|   256| F |
      256|    A | C |  <- najniższa gałąź w drzewie
    

    Np. 0100. Czytamy ciag bitów i dekodujemy. Bit 0 oznacza że mamy wybrać element po lewej stronie. Po lewej stronie w wierzchołku występuje 258. A więc skaczemy do wierzchołka 258, mamy tam dwie wartości B i 257, którą wybrać? Odczytujemy więc następny bit i on nam odpowie co mamy zrobić. Następny bit to 1, więc wybieramy z prawej strony element 257. Skaczemy pod 257, tam mamy 256 i F, odczytujemy bit, jest nim 0, wiec wybieramy z lewej 256. Skaczemy pod 256 tam mamy A i C, odczytujemy bit, jest nim 0, więc naszym elementem zdekodowanym jest A.

    8b. Budowa drzewa z kodów binarnych.

     - bierzemy bit i wstawiamy na 1-wsze wolne miejsce w drzewie (w zależności od
       wartości np. 0-lewy 1-prawy liść) wartość określającą nastepną wolną gałąź
     - kończymy wstawiając wartość 0..255 (A..Z)
    
    np 1.
    
                                 zakodowane
             drzewko           informacje A..H
             -------           ---------------
    
             0   1
             
    256|    257|259|                A  010
    257|     F |258|                B  011
    258|     A | B |                C  100
    259|    260|261|                D  101
    260|     C | D |                E  110
    261|     E |262|                F   00
    262|     G | H |                G 1110
                                    H 1111
    
    np 2.
             0   1
             
    256|     A |257|                A     0
    257|     B |258|                B    10
    258|    259|260|                C  1100
    259|     C | F |                D 11110
    260|    262|261|                E 11100
    261|     D |   |                F  1101
    262|     E | G |                G 11101
    

    P.S. balois by tego nie wiedział ;P

    Komentarze gości atari.area

    Momencik, uaktualniam...  

    Nie jesteś zalogowany. Tylko zarejestrowani i zalogowani użytkownicy mog± dodawać komentarze.

Lotharek.pl
Retronics
Sikor Soft

Szukaj

Wyszukiwarka przeszukuje zasoby atari.area, atariki oraz forum.

Twoliner

Momencik, uaktualniam...  .

Pamiętaj, żeby linki do Twolinera dodawać wyłącznie po skróceniu za pomocą serwisu tiny.pl. Jeśli coś Ciebie ominęło - skorzystaj z archiwum.

Network

konto

Nie jesteś zalogowany. Zaloguj się lub załóż konto

forum

Artykuły

Wywiady

Allegro

Jako, że Allegro.pl jest bardzo często odwiedzanym serwisem przez Atarowców, umiejscowiłem poniżej wyszukiwarkę produktów związanych z naszym kochanym Atari. Chcesz coś kupić - wystarczy wpisać w okienko poniżej.


Wystarczy wpisac czego szukamy i po chwili znajdujemy sie juz na Allegro.pl.