Implementacja obrazu monochromatycznego

0

Mam takie zadanie:

Proszę napisać klasę Image implementującą dwuwymiarowy obraz monochromatyczny (tzn.
obraz w odcieniach szarości).
Wskazówki:

  1. Obrazy monochromatyczne przechowywane są w pamięci komputerów jako
    dwuwymiarowa tablica, której elementami są jasności kolejnych pikseli obrazu.
    Jasność każdego piksela może być reprezentowana przez 8 bitową liczbę naturalną,
    tzn. liczbę z przedziału 0 – 255 (unsigned char). Liczba 0 oznacza kolor czarny, 255 -
    biały.
  2. W programie wygodnie jest zdefiniować własną, krótszą nazwę (np. byte) dla typu
    unsigned char.
  3. Dwuwymiarowa tablica ma być alokowana dynamicznie.
  4. Zauważ że konstruktor, konstruktor kopiujący oraz operator = mają pewne wspólne
    instrukcje np. alokujące pamięć. Programując należy unikać duplikacji kodu, a zatem
    te wspólne instrukcje powinny być napisane w jednym miejscu np. w osobnej funkcji
    initialize.
    Klasa Image powinna mieć następujące funkcje składowe:
    Na ocenę 3:
  5. Konstruktor:
    Image(int width, int height);
  6. Metodę nadającą tą samą jasność wszystkim pikselom w obrazie, np.:
    void fillInImage(unsigned char value)
  7. Metodę służącą do odczytu piksela. Metoda powinna poprawnie obsłużyć sytuacje gdy
    żądane indeksy wykraczają poza obszar obrazu. Zastanów się, jak można przekazać
    użytkownikowi informację o błędzie (przekroczeniu zakresu indeksów)
  8. Metodę służącą do zapisu piksela. Metoda powinna poprawnie obsłużyć sytuacje gdy
    żądane indeksy wykraczają poza obszar obrazu.
  9. Metody:
    int getWidth();
    int getHeight();
  10. Program powinien być napisany wielomodułowo (np. w plikach image.h, image.cpp
    oraz main.cpp)
  11. Należy tak zaprojektować klasę Image aby działała poprawnie również w sytuacji gdy
    użytkownik posługuje się pustymi obrazami, tzn. obrazami o wymiarach width = 0,
    height = 0, jak w poniższej sytuacji:
    Image image(0, 0);
    Image copiedImage(image);
    Image equaledImage(5, 4);
    EqualedImage = image;
    Na ocenę 4:
  12. Destruktor
  13. Konstruktor kopiujący
    10)Operator =
    Na ocenę 5:
  14. Funkcje służącą do utworzenia nowego obiektu klasy Image na podstawie fragmentu
    innego obiektu. Jako argumenty funkcja powinna przyjmować indeks początkowego
    oraz końcowego wiersza oraz początkowej oraz końcowej kolumny określające
    fragment który będzie wykorzystany do utworzenia nowego obiektu.
  15. Funkcję wyświetlającą obraz na ekranie.

Mimo iż nie jestem doświadczony to rozumiem w podstawach klasy, ale nie do końca rozumiem co ten program po kolei ma robić. Rozumiem, ze ma to być tablica dwuwymiarowa, zawierająca współrzędne x,y.
Funkcja void fillInImage(unsigned char value) to pewnie ma być progowanie jasności. Czy może mi ktoś pomóc, podpowiedzieć?

0

Program ma nic nie robić. Masz napisać tylko klasę. fillInImage to wypełnienie całej bitmapy jednolitym kolorem.

0

No to teraz już nic nie rozumiem :-| Nawet jeśli wezmę za przykład to jakie moduły maja istnieć i pominę main.cpp to i tak istnieje klasa oraz funkcje, a wewnątrz funkcji opisuje się co ma program robić. Ok może nie wymaga sie tu pokazania przykładu możliwości ale z założenia te funkcje do czegoś służą.

0

Tzn czego nie wiesz ? Przecież wszystko masz napisane co ma jaka metoda robić. Konkretniej pytaj bo się inaczej nie dogadamy. Najlepiej zacznij już pisać i na bieżąco opisuj swoje wątpliwości.

Napiszę ci szablon, a ty sobie uzupełnisz metody:

typedef unsigned char byte;

class Image
{
private:
  byte *pixels; //lub byte** pixels, zależy jak tablicę skonstruujesz, możesz użyć też vector
  int width;
  int height;
public:
   Image() { alloc(0, 0); }
   Image(int width, int height) { alloc(width, height); }
   Image(const Image& image) { alloc(0, 0); *this = image; }
   Image(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight)
   { 
      alloc(0, 0); 
      assignFrom(srcImage, srcLeft, srcTop, srcWidth, srcHeight); 
   }
   ~Image() { dealloc(); }
   Image& operator=(const Image& image) 
   { 
      assignFrom(image, 0, 0, image.getWidth(), image.getHeight(); 
   }
   int getWidth() const { return width; }
   int getHeight() const { return height; };

//do napisania przez ciebie
private:
   void alloc(int width, int height) { this->width = width; this->height = height; /*...alokacja...*/ } //utworzenie tablicy
   void dealloc(); //zniszczenie tablicy
public:
   void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight); //kopiuje zawartość innego obrazka
   byte pixel(int left, int top) const; //piksel na pozycji [left, top]
   void fillInImage(byte value); //wypełnienie jednolitym kolorem
   void print() const; //wyświetlenie
}
0

Właściwie już zacząłem pisać o co mi chodzi, ale skro już napisałeś szablon to dziękuję bardzo, na spokojnie przetrawię to i ewentualnie będę jeszcze pytał :).

0

Tak na początek to nie rozumiem po co aż tyle linijek dla konstruktora:

   Image() { alloc(0, 0); }
   Image(int width, int height) { alloc(width, height); }
   Image(const Image& image) { alloc(0, 0); *this = image; }
   Image(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight)
   {
      alloc(0, 0);
      assignFrom(srcImage, srcLeft, srcTop, srcWidth, srcHeight);
   }

Czy nie wystarczy jedna? Po prostu nazwaklasy (zmienne) {instrukcje};

0

Jakbyś nie zauważył, to są 4 konstruktory.

0

To nie jest "az tyle linijek" ... to są różne konstruktory zależne od przekazanych parametrów.(czyli nastepuje tutaj przeładowanie funkcji konstruktora) Jeśli ich nie chcesz to nie musisz używać (chociaż konstruktor kopiujący pewnie by się przydał ;p)

//kurcze za długo mialem otwarta zakladke w FF :P nie widzialem odpowiedzi @adf

0

Zrobilem sobie alloc i dealloc w ten sposob, nie wiem czy dobrze. Reszte niejasnosci dopisalem przy kodzie/

private:
   void alloc(int width, int height)
        {
                  int *tab;
                  tab = new int[width][height];
        }; //utworzenie tablicy
   void dealloc()
        {
                 delete [] tab;
        }; //zniszczenie tablicy
public:
   void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight); //kopiuje zawartość innego obrazka - nie rozumiem tego wszystkiego w nawiasie, bo chyba przy kopiowaniu sa parametry "źródło" i "cel"
   byte pixel(int left, int top) const; //piksel na pozycji [left, top] - to w ogole nie rozumiem do czego sluzy
   void fillInImage(byte value); //wypełnienie jednolitym kolorem - czy tu mam operowac na liczbach z zakresu 0-255 i po prostu zrobic funkcje ktora dla wszystkich pikseli przyporzadkuje jednakowa (podana) wartosc?
   void print() const; //wyświetlenie - jakiej funkcji uzyc do wyswietlenia piksela? np do wyswietlenia znakow mozna uzyc cout lub printf, a jakiej do tego?
0

Źle alokujesz. nie możesz zwykłemu wskaźnikowi przypisać dwuwymiarowej tablicy przy pomocy operatora new. Musisz zrobic wpierw tablice wskaźników, a potem dla każdego z nich stworzyć tablice intow. Możesz tez robić jedno wymiarowa tablice o rozmiarach x*y i odwoływać się do niej w odpowiedni sposób .... poczytaj na ten temat :)

Skoro alokacja jest zła to deallokacja tez będzie musiała wyglądać inaczej (chyba ze zdecydujesz się na metodę z jedno wymiarowa tablica)

void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight);
Ten konstruktor służy do tworzenia obrazu z FRAGMENTU już istniejącego

byte pixel(int left, int top) const;
To ma zwraca wartość pixela o tych wsp. (czyli jego kolor ...)

void fillInImage(byte value);
Skoro masz wypelnic caly obrazek kolorem o wartości value to jak sadzisz (value musi spełniać warunek : value >=0 ^ value <=255)

co do wyświetlania to ogranicza cie tylko twoja wyobraznia .... pod windowsem WinAPI daje możliwość kontroli nad kolorem wyświetlanym w konsoli (a za pixel mozesz uznac znak pusty - spacje - o tle danego koloru)
możesz tez wyrenderowac obraz za pomocą OpenCV, OpenGL, SDL'a i miliona innych bilbiotek ...

ps. mam pytanie, możesz powiedziec skad to zadnie ? studia, liceum ;> ?

0

Czy alloc i dealloc teraz jest dobrze?

private:
   void alloc(int width, int height)
        {
                  int **tab;
                  tab = new int*[width];
                  for (int i=0; i<width; ++width)
                  tab[i] = new int[height];
        }; //utworzenie tablicy
   void dealloc()
        {
                 for(int i=0; i<width; ++width)
                 delete [] tab[i];
                 delete [] tab;
        }; //zniszczenie tablicy
</cpp>
0

Wygląda na to że teraz jest ok .... btw. przydało by Ci sie trochę więcej samodzielnosci ...

0

Wiem, ale po zmianie programu na studiach na kierunku elektronika na semestrze II robiliśmy to co dawniej było na semestrze VII na specjalizacji, wcześniej poprzedzone podstawami i czasem na nie i to już przeszedłem jakoś, a teraz jestem na semestrze III, a starsi koledzy z kierunku informatyka też nie potrafią często pomóc. Już nie mówiąc o prowadzącym od którego nie dowiesz sie nic.

Poza tym poprawiłem przecież to co było źle i chciałem tylko wiedzieć czy jest ok. Dzięki wielkie za tą pomoc, którą do tej pory uzyskałem. teraz myślę nad dalszą częścią. Pozdro!

PS. Właśnie zauważyłem, że przypadkowo odpowiedziałem na Twoje pytanie umieszczone po edycji postu ;].

0
void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight);

No dobrze konstruktor, czy funkcja nie ważne, bo i tak nie wiem jak to zrobić, a przede wszystkim nie wiem dlaczego jest tam "Image&" oraz co oznacza "srcImage, srcLeft, srcTop", bo reszta to rozumiem, że szerokość i wysokość.

byte pixel(int left, int top) const;

Czyli rozumiem, że są to jakieś współrzędne x,y mówiące, który to piksel i funkcja ta ma zwrócić liczbę z zakresu 0-255, bym dowiedział się jaki jest kolor piksela? Jeśli tak to teraz tylko nie wiem czy dobrze rozumiem, że te współrzędne to numery elementów tej tablicy dwuwymiarowej tzn. tab [width, height] ?

void fillInImage(byte value);
Rezor napisał(a)

value musi spełniać warunek : value >=0 ^ value <=255

Z tego co się dowiedziałem, to mamy to zrobić tak, żeby wyświetlało np. X lub biały znak. Jeśli dobrze rozumiem to jest właśnie coś takiego jak progowanie jasności, o którym pisałem na początku, czyli ustalam próg np. 100 i wszystko co należy do zakresu (0;100> będzie wyświetlane jako X, a to co należy do zakresu (100;255> będzie wyświetlane jako spacja. Czy mój tok myślenia jest prawidłowy?

W tym momencie funkcja wyświetlająca będzie zawierać tylko "cout<<". Tylko nie wiem dlaczego ta funkcja wygląda tak

void print() const;

Czemu ma służyć "const" po deklaracji funkcji, także w przypadku funkcji "pixel"?
Nie może być void print (kolor)?

Tak to rozumiem:

byte pixel(int left, int top)
	}
		return tab[left, top];
	};
void fillInImage(byte value)
        {
		if(value<=100)
		value="X";
		else
		value=" ";
	};
void print(kolor)
	{
		fillinImage(kolor);
		cout<<kolor;
	};
0

Jeszcze raz pokolei:

void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight);

Ta funkcja ma za zadanie, skopiować fragment z już istniejącego obrazu przekazanego przez referencje do nowego obiektu. Width i Height to szerokość i wysokość obrazu, a left i top to pixel graniczny od którego ma się wykonywać kopiowanie.

Funkcja pixel wygląda ok (pomijajac to ze w ten sposob nie odnosimy sie do elementow tablicy .. :P)

Fill in image ma wypelnić całą tablice pixeli wartoscią przekazana jako argument:

void fillInImage(byte value)
{
for (int i=0; i<width; i++) //pierwszy wymiar tablicy - rzedy
{
for(int j = 0; j<height; j++) // drugi wymiar - kolumny
{
pixels[i][j] = value // twoja 2 wymiarowa tablica przechowujaca info o pixelach
}
}
};

Tak powinna mniej wiecej wyglądać.

Teraz też zauważyłem ze masz źle postawione warunki przy alokacji tablicy. Jako trzeci parametr pętli for powinieneś dać inkrementecje (lub post) zmiennej i, a nie zwiększać width :)

Co do twojego sposobu wyświetlania to moim zdaniem to troche bezsensu. Jesltes pewien ze masz przedstawic ten obraz wyłącznie w formie X'ów i spacji ?

0

Rzeczywiście nastąpiła pomyłka przy alokacji, juz poprawiłem:

   void alloc(int width, int height)																//utworzenie tablicy
        {
			int **tab;
            tab = new int*[width];
            for (int i=0; i<width; ++i)
            tab[i] = new int[height];
        };
   void dealloc()																					//zniszczenie tablicy
        {
            for(int i=0; i<width; ++i)
            delete [] tab[i];
            delete [] tab;
        };

Jestem pewny, że mam przedstawić obraz w ten sposób i skoro funkcja fillInImage ma wyglądać tak jak napisałeś, czyli nadawać rzeczywiście tą samą wartość to gdzie mam umieścić warunek, który będzie mówił o tym, że ma być to X lub spacja a nie ta podana wartość z zakresu 0-255?

Funkcja print chyba rzeczywiście jest bez sensu bo nie wyświetli mi całego obrazka, więc zrobiłem coś takiego tylko nie wiem jaki dać argument:

   void print() 																				//wyświetlenie obrazka
		{
			for (int i=0; i<width; i++)
				{
					for(int j = 0; j<height; j++)
						{
							byte pixels(i, j);
							if(tab[i][j]<=100)
							tab[i][j]="X";
							else
							tab[i][j]=" ";
							cout<<tab[i][j];
						}
				}
		};

Czy tak będzie dobrze?

A jeśli chodzi o funkcje pixel to powinno być tak zgadza się?:

byte pixels(int left, int top)
        }
                return tab[left][top];
        };

Co do funkcji assingFrom to próbowałem skupić się na przykładach konstruktorów kopiujących i operatorów przypisania, bo te zawierają ta funkcję. Jeśli dobrze zrozumiałem to będę musiał zrobić tam coś takiego:

if (&image != this)
    {
        **pixels = **(image.pixels);
        width = image.getWidth();
        height = image.getHeight();
    }
return *this

tylko co z wartościami srcLeft i srcTop? Dla operatora przypisania niby ok bo tam te wartości są równe 0, ale dla konstruktora kopiującego ma to już znaczenie. Nie moge cały czas tak do końca tego pojąć. Czy to chodzi o to, że trzeba przypisać nowy początek tzn. tab[0][0] = tab[srcleft][srctop] czyli określenie indeksów początkowego wiersza i kolumny? Jak to zrobić?
No i jęśli dobrze rozumiem to jest funkcja do zrobienia na ocenę 5, to czemu zawiera się ona w konstruktorze kopiującym i operatorze=, które sa na ocenę 4? :P

Jeszcze mam takie pytanie. Najpierw patrząc czego potrzebuję na ocenę 3 to mam:

  • nazwę dla typu (byte);
  • tablicę alokowaną dynamicznie;
  • konstruktor, a nawet dwa;
  • metody getWidth() i getHeight();
  • fillInImage (byte value);

A co miał autor na myśli pisząc o matedzie zapisu i dczytu piksela? Odczyt to chyba byte pixels(...), a zapis?

0

Moim zdaniem tablica pixels powinna przechowywać konkretne wartości odcienia pixela czyli od <0,255>
Natomiast funkcja print powinna sprawdzać wartość kolejnych pixeli i wyswietlac odpowiednie X lub spacje.

   void print()                                                                                                                                                                 
                {
                        for (int i=0; i<width; i++)
                                {
                                        for(int j = 0; j<height; j++)
                                                {
                                                        if(tab[i][j]<=100 && tab[i][j]>= 0) cout << "X"; //sprawdzasz czy wartość pixela miesci sie w przedziale
                                                        else if(tab[i][j]>100 && tab[i][j]<=255) cout << " "; // jw
                                                        else cout << "O"; //na wypadek jeśli z jakiegoś powodu wart. pixela będzie poza granicami do czego nie powinieneś dopuścić
                                                }
                                }
                };

Zapis pixela to czynność przeciwna do odczytu :P czyli:

void PutPixel(int x, int y, byte value)
{
pixels[x][y] = value;
}

Co do assignFrom to przepisz porostu ręcznie wartości pixeli z przekazanego obiektu do twojego

P.S. Chyba troche sie meczysz z tym projektem :P nie lepiej zlecić komuś napisanie tego ? :P

0
Rezor napisał(a)

Moim zdaniem tablica pixels powinna przechowywać konkretne wartości odcienia pixela czyli od <0,255>

No tak i takie będzie przechowywać :). Dziękuję za zapis :).

Rezor napisał(a)

Co do assignFrom to przepisz porostu ręcznie wartości pixeli z przekazanego obiektu do twojego

Czy o to chodzi? :

   void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight) 	//kopiuje zawartość innego obrazka
		{
			for (int i=0; i<=(srcWidth-srcLeft); i++)
				{
					for (int j=0; j<=(srcHeight-srcTop); j++
						{
							nowa[i][j] = tab[i+srcLeft][j+srcTop];
						}
				}
		}
Rezor napisał(a)

P.S. Chyba troche sie meczysz z tym projektem :P nie lepiej zlecić komuś napisanie tego ? :P

No niby lepiej ale wtedy juz nic bym nie wyniósł z tego :P.

Odpowiedz mi proszę jeszcze na to pytanie bo mnie nurtuje, a poza tym spodziewam się takiego od prowadzącego :P :

No i jęśli dobrze rozumiem to jest funkcja do zrobienia na ocenę 5, to czemu zawiera się ona w konstruktorze kopiującym i operatorze=, które sa na ocenę 4?

0
seba_fonia napisał(a)

Odpowiedz mi proszę jeszcze na to pytanie bo mnie nurtuje, a poza tym spodziewam się takiego od prowadzącego :P :

No i jęśli dobrze rozumiem to jest funkcja do zrobienia na ocenę 5, to czemu zawiera się ona w konstruktorze kopiującym i operatorze=, które sa na ocenę 4?
dlatego, że:

seba_fonia napisał(a)

Programując należy unikać duplikacji kodu
Utworzenie kopii całego obrazka jest szczególnym przypadkiem utworzenia obrazka na podstawie fragmentu innego obrazka - fragmentem tym jest cały obrazek. Metoda tworząca obrazek na podtawie fragmentu wspaniale się nadaje do utworzenia obrazka na podstawie całości.

seba_fonia napisał(a)

void alloc(int width, int height) //utworzenie tablicy
{
int *tab;
tab = new int
[width];
for (int i=0; i<width; ++i)
tab[i] = new int[height];
};
Deklarujesz wskaźnik 'tab' wewnątrz metody. Jak myślisz co się stanie z tym wskaźnikiem jak metoda się skończy ? :> Ten wskaźnik musi być zapamiętany przez obiekt klasy, musi być jej polem. poza tym miałeś używać nie 'int' a 'unsigned char' który krócej nazwaliśmy 'byte'.

class Image
{
private:
  byte **tab; //Tu ma byc ten wskaznik, ma nalezec do obiektu klasy. Wczesniej nazwałem go pixels.
  int width;
  int height;
//...
private:
  void alloc(int width, int height)
        {
            //o tym tez zapomniales
            this->width = width; 
            this->height = height;

            tab = new int*[width];
            for (int i=0; i<width; ++i)
            tab[i] = new int[height];
        };

W 'assignFrom' powino się znaleźć:

  • dealokacja starej tablicy
  • alokacja nowej tablicy o zadanych wymiarach (srcWidth, srcHeight)
  • przekopiowanie w pętelkach zawartości obrazka źródłowego do nowej tablicy, fragment do skopiowania jest określony parametrami srcLeft, srcTop, srcWidth, srcHeight,
0

Acha no tak, wreszcie zrozumiałem dlaczego w jednym miejscu było pixel, a w drugim pixels. Dziękuję już rozumiem - wskaźnik poza tą metodą by nie istniał, a jest potrzebny.

edit: Czy teraz hest dobrze, czy nadal nie rozumiem idei tej funkcji?:

   void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight) 	//kopiuje zawartość innego obrazka
		{
			dealloc();
			alloc(srcWidth, srcHeight);
			for (int i=0; i<srcWidth; ++i)
				{
					for (int j=0; j<srcHeight; ++j)
					{
						tab[i][j]=int(image.tab[srcLeft][srcTop];
					}
				}			
		}

Jeszcze nie rozumiem czemu w konstruktorze kopiującym tym drugim jest na początku alloc(0, 0), skoro zaraz będzie dealloc() i nowa alokacja.

Kolejna rzecz to pierwszy konstruktor kopiujący. Nie wiem jak go sobie wytłumaczyć, poczytałem trochę, nawet zrobiłem na tej podstawie coś takiego:

        Image(const Image& image)
            {
                this->tab = new int( *( image.tab ) ); 
            }

Oczywiście wiem, że to nie ma sensu bo mam unikać duplikacji kodu, dlatego jest tam na początku alloc(0, 0), a później *this = image. Ale jakoś nie umiem tego zrozumieć. Dlatego napisałem jeszcze taki kod:

Image(const Image& image)
{
	alloc(image.width, image.height);
	for(int i=0;i<image.width;i++)
		{
			for(int j=0; j<image.height; i++)
			{
				tab =image.tab;
			}
		}
	width=image.width;
	height=image.height
} 

Mam też pytanie, czy jeśli na początku deklaruję sobie byte **tab, to nie muszę wszędzie już używać byte, np. w alloc, gdzie wewnątrz zamiast byte jest int?

Ostatnie pytanie - czemu w funkcji alloc musi być:

this->width = width;
this->height = height;
0

Jeśli możesz to napisz mi tylko jeszcze czy ta funkcja assignFrom jest dobrze
Jest źle, bo tylko skopiowałeś wskaźnik. Obydwa obrazki współdzieliłyby tablicę pikseli i jeśli zmieniłbyś jakiś piksel na jednym to również piksel na drugim obrazku by się zmienił. Napisałem post wcześniej jak powinna wyglądać ta metoda.

Na ocenenę 4 metoda assignFrom nie posiadałaby parametrów srcLeft, srcTop, srcWidth, srcHeight, tylko kopiowałaby cały obrazek. Właściwie mogłaby zastąpić operator przypisania, tzn musiałbyś przenieść jej całą zawartość do operatora przypisania, a ją samą wywalić.

0

Chyba nie zauważyłem że jest post na kolejnej stronie i edytowałem poprzedni. W każdym razie zmieniłem assignFrom i dodałem jeszcze kilka pytań.

Z góry dzięki :)

0
seba_fonia napisał(a)

Czy teraz hest dobrze
prawie

seba_fonia napisał(a)

tab[i][j]=int(image.tab[srcLeft][srcTop]
Zawszę będziesz kopiować ten sam piksel "image.tab[srcLeft][srcTop]" ? :>

Zapis alloc(0,0); powoduje przypisanie polom tab, width i height domyślnych wartości oznaczających pustą tablicę.

seba_fonia napisał(a)

Jeszcze nie rozumiem czemu w konstruktorze kopiującym tym drugim jest na początku alloc(0, 0), skoro zaraz będzie dealloc() i nowa alokacja.
No właśnie, zaraz będzie dealloc a nic nie jest zainicjowane i dealloc by się wysypał, bo pola tab, width, height są niezaincjowane. Alloc je inicjuje. Wiąże się to następnym i ostatnim pytaniem.

seba_fonia napisał(a)

Kolejna rzecz to pierwszy konstruktor kopiujący. Nie wiem jak go sobie wytłumaczyć
Na początku jest alloc(0,0) aby zainicjować zmienne tab, width i height (coby następny dealloc się nie pogubił), a później to już tylko zwykłe przypisanie operatorem przypisania.

seba_fonia napisał(a)

Mam też pytanie, czy jeśli na początku deklaruję sobie byte **tab, to nie muszę wszędzie już używać byte, np. w alloc, gdzie wewnątrz zamiast byte jest int?
Tak, racja ! Przeoczyłem to.

seba_fonia napisał(a)

Ostatnie pytanie - czemu w funkcji alloc musi być:
this->width = width;
this->height = height;
dlatego, że funkcja alloc inicjuje wszystkie pola (tab, width i height ), tak to sobie ja wymyśliłem, takie rozwiązanie pasuje tutaj. Np. w konstruktorze musisz zainicjować pola width i height na jakieś zadane wartości - wymiary obrazka. Zamiast pisać tak: (kilkakrotnie)

width = szerokosc;
height = wysokosc;
alloc(szerokosc, wysokosc);

niech to przypisanie robi przy okazji metoda alloc, wtedy wystarczy samo

alloc(szerokosc, wysokosc)
0
adf88 napisał(a)
seba_fonia napisał(a)

Czy teraz hest dobrze
prawie

seba_fonia napisał(a)

tab[i][j]=int(image.tab[srcLeft][srcTop]
Zawszę będziesz kopiować ten sam piksel "image.tab[srcLeft][srcTop]" ? :>

A no rzeczywiście przeoczyłem to [green]. Jeszcze zmieniłem image na srcImage bo to chyba też był błąd (nie zgadzałoby się z parametrem. Teraz jest ok? :

 void assignFrom(const Image& srcImage, int srcLeft, int srcTop, int srcWidth, int srcHeight) 	//kopiuje zawartość innego obrazka
		{
			dealloc();
			alloc(srcWidth, srcHeight);
			for (int i=0; i<srcWidth; ++i)
				{
					for (int j=0; j<srcHeight; ++j)
					{
						tab[i][j]=int(srcImage.tab[srcLeft+i][srcTop+j];
					}
				}			
		}
adf88 napisał(a)

Zapis alloc(0,0); powoduje przypisanie polom tab, width i height domyślnych wartości oznaczających pustą tablicę.

seba_fonia napisał(a)

Jeszcze nie rozumiem czemu w konstruktorze kopiującym tym drugim jest na początku alloc(0, 0), skoro zaraz będzie dealloc() i nowa alokacja.
No właśnie, zaraz będzie dealloc a nic nie jest zainicjowane i dealloc by się wysypał, bo pola tab, width, height są niezaincjowane. Alloc je inicjuje. Wiąże się to następnym i ostatnim pytaniem.

No już rozumiem - gdyby nie to że wewnątrz tych metod używam funkcji assignFrom, która zaczyna się od dealloc(), to mógłbym pominąć alloc(0, 0), ale w tym przypadku musi być :). Ale czy w takim razie nie przeoczyłeś jeszcze jednej rzeczy? :> Wewnątrz operator= też chyba powinno znajdować alloc(0, 0) prawda? :)

adf88 napisał(a)
seba_fonia napisał(a)

Kolejna rzecz to pierwszy konstruktor kopiujący. Nie wiem jak go sobie wytłumaczyć
Na początku jest alloc(0,0) aby zainicjować zmienne tab, width i height (coby następny dealloc się nie pogubił), a później to już tylko zwykłe przypisanie operatorem przypisania.

Acha czyli tak rozumując po mojemu :P to *this jest wskaźnikiem na kopie, którą chcemy utworzyć, a image wzorem, z którego tworzymy tak (czyli kopiowanie zajdzie w ten sposób: Image I2(I1) co stworzy obraz I2 jako kopię obrazu-wzoru I1)? Jeśli tak to rzeczywiście najprostszy sposób, ale tak z ciekawości - czy któryś z tych konstruktorów które ja napisałem na podstawie pewnych wyczytanych wiadomości jest dobry?

0

Wewnątrz operator= też chyba powinno znajdować alloc(0, 0) prawda?
Nie. Operator przypisania może być wywołany na obiekcie już utworzonym, czyli zainicjowanym. Taka inicjajca "alloc(0,0)" tyczy się jedynie konstruktorów, gdzie trzeba przypisać wartości nowo utworzonym polom. Poza tym "alloc(0,0)" wewnątrz operatora przypisania zamazałby starą tablicę (stary wskaźnik) i nie mogłaby on być zniszczona, zostałaby w pamięci nikomu nie potrzebna, tzw. wyciek pamięci.

Acha czyli tak rozumując po mojemu to *this jest wskaźnikiem na kopie, którą chcemy utworzyć, a image wzorem, z którego tworzymy tak (czy odwrotnie tzn. *this jest wskaźnikiem na wzór, a image kopią)?
this jest wskaźnikiem, *this to już dereferencja tego wskaźnika, czyli odwołanie się do obiektu wskazywanego. W przypadku naszego konstruktora obiektem tym jest nowo tworzona kopia (obiekt konstruowany konstruktorem). Obiekt kopiowany jest przekazany jako parametr image.
Wewnątrz dowolnej metody wskaźnik this wskazuje na obiekt którego metoda została wywołana, np:

class klasa
{
public:
   void metoda();
};

klasa obiekt;
obiekt.metoda(); //<-- po wywołaniu tej metody wskaźnik 'this' wewnątrz niej wskazuje na obiekt 'obiekt'

Inny przykład:

class klasa
{
private:
   int pole;
public:
   void metoda()
   {
      pole = 1;
      //powyższe znaczy tyle samo, co poniższe
      this->pole = 1;
   }

//ale jesli pole i parametr nazywają się tak samo, to już jest inaczej:
   void metoda(int pole)
   {
      this->pole = 1; //tutaj odwolujemy sie do pola obiektu
      pole = 1; //uwaga ! tutaj odwołujemy się do parametru metody ! nie do pola obiektu
   }
};

dlatego pisałem "this->width = width;", bo jedno width to pole klasy, a drugie width to parametr metody.

czy któryś z tych konstruktorów które ja napisałem na podstawie pewnych wyczytanych wiadomości jest dobry?
No nie za bardzo, coś koło tego ;) z tym, że to tylko zbędne powtarzanie kodu.

//EDIT:
aha, metoda assignFrom już wygląda na OK

0

Rzeczywiście przepraszam, że wskazałem na Twój błąd, którego nie było. Nie pomyślałem o tym.

Ogólnie wszystko już rozumiem. Z tym *this chodziło mi właśnie o to, ocz czym napisałeś, tylko ubrałem to w nieodpowiednie słowa, no ale do programisty mi jeszcze daleko.

Nie wiem tylko w takim razie do czego ma służyć ten pierwszy konstruktor kopiujący, bo wydaje się działać identycznie jak operator przypisania. Czy to chodzi o to, że konstruktor kopiujący zadziała przy tworzeniu nowego obiektu na podstawie drugiego tak jak podawałem przykład wcześniej, a operator= używamy, gdy są już dwa obiekty, tylko chcemy by były identyczne?

Tu wstawiam cały programik, tylko pozmieniałem sobie zmienne po swojemu. Jeśli jest jakiś błąd to napiszcie proszę:

typedef unsigned char byte;

class Image
{
private:
   byte **tab;											                                              //tablica przechowująca wartości pikseli obrazu
   int width;
   int height;
   void alloc(int width, int height)																                 //utworzenie tablicy
               {
	                this->width = width;
                        this->height = height;
	                tab = new byte*[width];
                        for (int i=0; i<width; ++i)
                        tab[i] = new byte[height];
               }
   void dealloc()																				     //zniszczenie tablicy
               {
                        for(int i=0; i<width; ++i)
                        delete [] tab[i];
                        delete [] tab;
                }
public:
   Image()																							//konstruktor
		{
			alloc(0, 0);
		}
   Image(int width, int height)																		                 //konstruktor
		{
			alloc(width, height);
		}
   Image(const Image& image)																              //konstruktor kopiujący
		{
			alloc(0, 0);
			*this = image;
		}
   Image(const Image& image, int x, int y, int w, int h)				                                                                        //konstruktor kopiujący
		{
			alloc(0, 0);
			assignFrom(image, x, y, w, h);
		}
   ~Image()																							//destruktor
		{
			dealloc();
		}
   Image& operator=(const Image& image)																//operator przypisania
		{
			assignFrom(image, 0, 0, image.getWidth(), image.getHeight());
		}
   int getWidth() const
		{
			return width;
		}
   int getHeight() const
		{
			return height;
		}
   void assignFrom(const Image& image, int x, int y, int w, int h) 	                                                                  //kopiuje zawartość innego obrazka
		{
			dealloc();
			alloc(w, h);
			for (int i=0; i<w; ++i)
				{
					for (int j=0; j<h; ++j)
					{
						tab[i][j]=int(image.tab[x+i][y+j];
					}
				}			
		}
   byte getPixel(int x, int y) 																	//zwraca wartość (kolor) piksela na pozycji [left, top]
		}
			return tab[x][y];
		}
   void putPixel(int x, int y, byte value)															                     //zapis piksela
		{
			tab[x][y] = value;
		}
   void fillInImage(byte value)														     //nadaje ten sam kolor wszystkim pikselom
		{
			for (int i=0; i<width; i++)
				{
					for(int j = 0; j<height; j++)
						{
							tab[i][j] = value;
						}
				}
		}
   void print() 																			          //wyświetlenie obrazka
		{
			for (int i=0; i<width; i++)
				{
					for(int j = 0; j<height; j++)
						{
							byte getPixel(i, j);
							if(tab[i][j]>=0 && tab[i][j]<=100)
							cout<<"X";
							else if(tab[i][j]>100 && tab[i][j]<=255)
							cout<<" ";
							else
							cout<<"!";
						}
				}
		}
}
0
Image img1; //konstruktor domyślny
Image img2(img1); //konstruktor kopiujący
Image img3 = img1; //konstruktor kopiujący, ten sam efekt co powyżej
img3 = img2; //operator przypisania, przypisujemy nową wartość do już istniejącego obiektu

Jeszcze w metodzie print:

byte getPixel(i, j);
błędne i zbędne

tab[i][j]>=0
zbędne

tab[i][j]<=255
zbędne
Typ unsigned char (nasz byte) może przyjąc wartości jedynie z zakresu 0..255, więc nie trzeba tych granic sprawdzać

Ciekawym pomysłem na wyświetlenie obrazka mogłoby być skożystanie z techniki a'la ascii art. Można sobie zrobić tablicę znaków o róznej "jasności", np spcja czarny, kropka już trochę jaśniejszy itd.

0
adf88 napisał(a)
Image img1; //konstruktor domyślny
Image img2(img1); //konstruktor kopiujący
Image img3 = img1; //konstruktor kopiujący, ten sam efekt co powyżej
img3 = img2; //operator przypisania, przypisujemy nową wartość do już istniejącego obiektu

No czyli tak jak zrozumiałem [green]. Super!

adf88 napisał(a)

Jeszcze w metodzie print:

byte getPixel(i, j);
błędne i zbędne

Rzeczywiście nie wykasowałem tego. Dzięki :).

adf88 napisał(a)

tab[i][j]>=0
zbędne

tab[i][j]<=255
zbędne
Typ unsigned char (nasz byte) może przyjąc wartości jedynie z zakresu 0..255, więc nie trzeba tych granic sprawdzać

Tu chyba Ty i Rezor macie inne zdanie. Ja stoje chyba na stanowisku, że to nie jest zbędne, gdyż mimo tego, że ten typ przyjmuje wartości z zakresu 0-255, to w poleceniu jest wyraźnie napisane, że mamy jakoś dać użytkownikowi znać, że granice zostały przekroczone (w tym przypadku cout<<"!").

adf88 napisał(a)

Ciekawym pomysłem na wyświetlenie obrazka mogłoby być skożystanie z techniki a'la ascii art. Można sobie zrobić tablicę znaków o róznej "jasności", np spcja czarny, kropka już trochę jaśniejszy itd.

Fakt to byłoby ciekawe :). W sumie przerobić to nie będzie trudno bo to tylko kwestia dopisania kilku progów, by było więcej zakresów i dla każdego inny znak.

0

Typ unsigned char (nasz byte) może przyjąc wartości jedynie z zakresu 0..255, więc nie trzeba tych granic sprawdzać

Teoretycznie tak. Jednak to też zależy w jaki sposób będziemy pojedynczym pixelom nadawać wartość (czyli punkt 4. dla oceny 3 :P ). Jeśli tam już nastąpi sprawdzenie podanej wartości to rzeczywiście jest to zbędne :) i jest to mój błąd.

Edit. Przemyslalem to i @adf ma racje :) sprawdzenie przed wyswietleniem nie ma sensu i jest to moj blad. Jednak sprawdzac nalezy przed zapisaniem wart. Pixela w metodzie putPixel()

0
Rezor napisał(a)

Przemyslalem to i @adf ma racje :) sprawdzenie przed wyswietleniem nie ma sensu i jest to moj blad. Jednak sprawdzac nalezy przed zapisaniem wart. Pixela w metodzie putPixel()

No tak teraz to mi się wszystko zgadza :-). Tylko ciekawe czemu w poleceniu nie tylko przy metodzie zapisu piksela, ale i przy metodzie odczytu piksela napisali o tym poprawnym obsłużeniu sytuacji gdy indeksy wykraczają poza obszar. Przecież obraz zapisany będzie już poprawny, no to przy odczycie nie będzie wartości spoza zakresu [???].

No to będzie tak:

   void putPixel(int x, int y, byte value)                                                                                                                                             //zapis piksela
                {
                        if(value<0 && value>255)
                        {
                                cout<<"Blad! Wartosc nie nalezy do zakresu 0-255.";
                                cout<<"Dokonaj zapisu ponownie, podajac wlasciwa wartosc.";
                        }
                        else
                        tab[x][y] = value;
                }
   void print()                                                                                                                                                                   //wyświetlenie obrazka
                {
                        for (int i=0; i<width; i++)
                                {
                                        for(int j = 0; j<height; j++)
                                                {
                                                        if(tab[i][j]<=100)
                                                        cout<<"X";
                                                        else
                                                        cout<<" ";
                                                }
                                }
                }

Czy teraz już wszystko jest poprawne?

1 użytkowników online, w tym zalogowanych: 0, gości: 1