2017-04-18 1 views
0

J'essaye d'écrire le dossier de PPM sur le disque. PPM est un simple format d'image qui se compose d'en-tête d'image ASCII et tableau d'octets de pixels:std :: ofstream ajoute le retour de chariot (CR; r) après n automatiquement

P6\n 
width height\n 
255\n 
[width*height*3 bytes total] 

C'est ma classe de PPM (simplifié):

class PPMImage 
{ 
protected: 
    friend std::istream& operator >>(std::istream &inputStream, PPMImage &other); 
    friend std::ostream& operator <<(std::ostream&, const PPMImage&); 
    size_t width; 
    size_t height; 
    // eg. "P6" 
    std::string magicNumber; 
    // Normally 255 
    uint16_t maxBrightness; 
    std::vector<std::vector<ImagePixel>> pixels; 
}; 

Voilà comment j'écrire l'image std::ofstream :

std::ostream& operator <<(std::ostream &output, const PPMImage &other) { 
    // Writing header - THIS IS WHERE THE PROBLEM IS! 
    output<<"P6\n"<<other.width<<'\n'<<other.height<<'\n'<<other.maxBrightness<<'\n'; 
    // The rest is pretty much irrelevant 
    size_t position = output.tellp(); 
    output.seekp(position+other.width*other.height*3); 
    // Force the stream to be specific size 
    const char zero = 200; 
    output.write(&zero, 1); 
    // Write the image 
    output.seekp(position); 
    for(size_t y=0, yl=other.height; y<yl; ++y) { 
     for(size_t x=0, xl=other.width; x<xl; ++x) { 
      output.write((char*)&(other.pixels[y][x].r), 1); 
      output.write((char*)&(other.pixels[y][x].g), 1); 
      output.write((char*)&(other.pixels[y][x].b), 1); 
     } 
    } 
    return output; 
} 

Voilà comment j'utilise cette API:

std::ofstream out; 
out.open("copy.ppm"); 
if(!out.is_open()) { 
    // error and exit here 
} 
out<<image; 
out.close(); 

L'image semble ok, à l'exception du fait que ofstream ajoute \r avant chaque \n dans l'en-tête:

P6\r\n 
width height\r\n 
255\r\n 
[width*height*3 bytes total] 

Cela est inacceptable. J'ai essayé de changer le code d'initialisation comme ceci:

std::ofstream out("copy.ppm", std::ios::binary); 
// I wonder why I have to mention "copy.ppm" twice... 
out.open("copy.ppm"); 

Mais cela crée juste le fichier vide. Quelqu'un peut-il expliquer comment écrire correctement PPM wile sans retour chariot?

En d'autres termes: Comment correctement initialiser le ofstream afin qu'il écrit sans \r?

+0

Le binaire ios :: est correct, mais il est déjà ouvert. Vous l'ouvrez à nouveau sans le mode binaire? Cela fonctionne bien pour moi si je dis juste si (out.is_open()) out << "bonjour \ n"; À la réflexion, non, je ne peux pas reproduire votre problème.Il continue même \ n même si j'appelle une seconde fois sans spécifier le mode, ce qui me surprend. –

+0

J'ai un fichier vide et je peux le reproduire. Peut-être que ce sont les fenêtres spécifiques? –

+0

Je ne suis pas allé à l'extrême d'appeler un opérateur ostream. Je vais essayer de reproduire le tout. –

Répondre

1

En ouvrant de manière incorrecte le fichier une deuxième fois, vous placez le flux dans un état d'échec. Il suffit d'appeler clear() pour que cela fonctionne, mais ce n'est pas idéal.

#include <fstream> 
#include <iostream> 

class CustomObject{ 
public: 
    std::string message; 
    explicit CustomObject(const std::string &text) : message(text) {} 
    friend std::ostream& operator <<(std::ostream&, const CustomObject&); 
}; 

std::ostream& operator <<(std::ostream &output, const CustomObject &other) { 
    if (output.fail()){ 
     std::cout << "the stream is in a fail state due to the bad open" << std::endl; 
     output.clear(); 
    } 

    output << "P6\n" << other.message.c_str() << '\n'; 
    return output; 
} 

int main() 
{ 
    std::string filename("something.ppm"); 
    std::ofstream out(filename, std::ios::binary); 
    out.open(filename); 
    out << CustomObject("Hello"); 
} 

La bonne façon d'ouvrir votre fichier est de passer tous les arguments ensemble, le nom de fichier et le mode, où vous avez choisi de le mettre. Soit dans le constructeur, soit avec open, mais pas les deux. Il suffit donc d'utiliser votre code d'origine ainsi que le mode correct pour Windows.

std::ofstream out; 
out.open("copy.ppm", std::ios::binary); 
if(!out.is_open()) { 
    // error and exit here 
} 
out<<image; 
out.close(); 
1

Comme vous l'avez compris, l'utilisation de std :: ios :: binary est la solution. Le constructeur std :: ofstream doit ouvrir le fichier, supprimez l'appel à out.open().

+0

J'ai compris que lorsque j'utilise ce drapeau, je reçois un fichier vide en sortie. Je l'utilise probablement mal. –

0

Ce que vous voyez est un artefact de Windows. Windows utilise les paires \r\n (alias Carriage Return/Line Feed ou 0x0D 0x0A) pour marquer la fin de la ligne. Le reste de l'univers utilise seulement \n par lui-même. Cela conduit au mode texte Windows fou pour les fichiers, qui traduit toutes les occurrences de \r\n dans un fichier à un seul \n lors de la lecture. En écriture, il traduit toutes les occurrences de \n en une paire \r\n. L'ouverture de votre fichier en mode std::ios::binary empêche cette traduction.

std::ofstream out("copy.ppm", std::ios::binary); 
// I wonder why I have to mention "copy.ppm" twice... 
out.open("copy.ppm"); 

Vous n'avez pas à appeler l'ouvrir deux fois. Pourquoi avez-vous pensé que vous faites? Cela va gâcher votre objet ofstream parce que [i]f the stream is already associated with a file (i.e., it is already open), calling this function fails. (http://www.cplusplus.com/reference/fstream/ofstream/open/) Donc, ne faites pas cela.

+1

"* Le reste de l'univers utilise juste" \ n' par lui-même * "- en fait tout le monde ne le fait pas. Certaines plates-formes utilisent '\ r' au lieu de' \ n'. Donc, vous avez 3 possibilités de traiter: 1) bare-CR, 2) bare-LF, et 3) CR + LF. –

+0

J'ai utilisé le code exact dans votre message et il produit un fichier vide. Après tout, c'est dans ma question aussi. –

+0

J'ai cité votre code pour le référencer.