Read a binary file (jpg) to a string using c++ - c++

I need to read a jpg file to a string. I want to upload this file to our server, I just find out that the API requires a string as the data of this pic. I followed the suggestions in a former question I've asked Upload pics to a server using c++ .
int main() {
ifstream fin("cloud.jpg");
ofstream fout("test.jpg");//for testing purpose, to see if the string is a right copy
ostringstream ostrm;
unsigned char tmp;
int count = 0;
while ( fin >> tmp ) {
++count;//for testing purpose
ostrm << tmp;
}
string data( ostrm.str() );
cout << count << endl;//ouput 60! Definitely not the right size
fout << string;//only 60 bytes
return 0;
}
Why it stops at 60? It's a strange character at 60, and what should I do to read the jpg to a string?
UPDATE
Almost there, but after using the suggested method, when I rewrite the string to the output file, it distorted. Find out that I should also specify that the ofstream is in binary mode by ofstream::binary. Done!
By the way what's the difference between ifstream::binary & ios::binary, is there any abbreviation for ofstream::binary?

Open the file in binary mode, otherwise it will have funny behavior, and it will handle certain non-text characters in inappropriate ways, at least on Windows.
ifstream fin("cloud.jpg", ios::binary);
Also, instead of a while loop, you can just read the whole file in one shot:
ostrm << fin.rdbuf();

You shouldn't read the file to a string because it is legal for a jpg to contain values that are 0. However in a string, the value 0 has a special meaning (it's the end of string indicator aka \0). You should instead read the file into a vector. You can do this easily like so:
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
int main(int argc, char* argv[])
{
std::ifstream ifs("C:\\Users\\Borgleader\\Documents\\Rapptz.h");
if(!ifs)
{
return -1;
}
std::vector<char> data = std::vector<char>(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
//If you really need it in a string you can initialize it the same way as the vector
std::string data2 = std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
std::for_each(data.begin(), data.end(), [](char c) { std::cout << c; });
std::cin.get();
return 0;
}

Try opening the file in binary mode:
ifstream fin("cloud.jpg", std::ios::binary);
At a guess, you were probably trying to read the file on Windows and the 61st character was probably 0x26 -- a control-Z, which (on Windows) will be treated as marking the end of the file.
As far as how to best do the reading, you end up with a choice between simplicity and speed, as demonstrated in a previous answer.

Related

Using std::find to find chars read from binary file and cast to a std::string in a std::vector<string> creates this inpredictible behaviour?

Sorry for the long headline. I couldn't know how to describe it in short words.
Would you care to recreate the problem i am going through?
You can use any wav file to read.
I am trying to query the chunks in a wav file here, this is the simplified version of the code, but i think it might be enough to recreate if there is a problem.
I use a mac, and compile with g++ -std=c++11.
When i run this code and don't include the line std::cout << query << std::endl; then std::find(chunk_types.begin(), chunk_types.end(), query) != chunk_types.end() returns 0 in all iterations. But i know the binary file contains some of these chunks. If i include the line then it works properly, but that is also not predictable lets say it works properly sometimes.
I am a bit perplexed am i doing anything wrong here?
#include <fstream>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main(){
std::vector<std::string> chunk_types{
"RIFF","WAVE","JUNK","fmt ","data","bext",
"cue ","LIST","minf","elm1",
"slnt","fact","plst","labl","note",
"adtl","ltxt","file"};
std::streampos fileSize;
std::ifstream file(/* file path here */, std::ios::binary);
file.seekg(0, std::ios::beg);
char fileData[4];
for(int i{0};i<100;i+=4){ //100 is an arbitrary number
file.seekg(i);
file.read((char*) &fileData[0], 4);
std::string query(fileData);
std::cout << query << std::endl;
/* if i put this std::cout here, it works or else std::find always returns 0 */
if( std::find(chunk_types.begin(), chunk_types.end(), query) != chunk_types.end() ){
std::cout << "found " + query << std::endl;
}
}
return 0;
}
std::string query(fileData) uses strlen on fileData to find its terminating 0, but doesn't find one because fileData is not zero-terminated and continues searching for 0 up the stack until it finds it or hits inaccessible memory past the end of the stack and causes SIGSEGV.
Also file.read can read fewer symbols than expected, gcount must be used to extract the actual number of characters last read:
A fix:
file.read(fileData, sizeof fileData);
auto len = file.gcount();
std::string query(fileData, len);
A slightly more efficient solution is to read directly into std::string and keep reusing it to avoid a memory allocation (if no short string optimisation) and copying:
std::string query;
// ...
constexpr int LENGTH = 4;
query.resize(LENGTH);
file.read(&query[0], LENGTH);
query.resize(file.gcount());

How to write to middle of a file in C++?

I think this should be quite simple, but my googling didn't help so far... I need to write to an existing file in C++, but not necessarily at the end of the file.
I know that when I just want to append text to my file, I can pass the flag ios:app when calling open on my stream object. However, this only let's me write to the very end of the file, but not into its middle.
I made a short program to illustrate the issue:
#include <iostream>
#include <fstream>
using namespace std;
int main () {
string path = "../test.csv";
fstream file;
file.open(path); // ios::in and ios::out by default
const int rows = 100;
for (int i = 0; i < rows; i++) {
file << i << "\n";
}
string line;
while (getline(file, line)) {
cout << "line: " << line << endl; // here I would like to append more text to certain rows
}
file.close();
}
You cannot insert in the middle of the file. You have to copy the old file to a new file and insert whatever you want in the middle during copying to the new file.
Otherwise, if you intend to overwrite data/lines in the existing file, that is possible by using std::ostream::seekp() to identify the position within the file.
You could write to the end and swap lines until it ends up in the right position.
Here's what I had to do.
Here's the test.txt file before:
12345678
12345678
12345678
12345678
12345678
Here's a sample of my program
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
fstream& goToLine(fstream& file, int line){
int charInLine = 10; //number of characters in each line + 2
//this file has 8 characters per line
int pos = (line-1)*charInLine;
file.seekg(pos);
file.seekp(pos);
return file;
}
fstream& swapLines(fstream& file, int firstLine, int secondLine){
string firstStr, secondStr;
goToLine(file,firstLine);
getline(file,firstStr);
goToLine(file,secondLine);
getline(file,secondStr);
goToLine(file,firstLine);
file.write(secondStr.c_str(),8); //Make sure there are 8 chars per line
goToLine(file,secondLine);
file.write(firstStr.c_str(),8);
return file;
}
int main(){
fstream file;
int numLines = 5; //number of lines in the file
//open file once to write to the end
file.open("test.txt",ios::app);
if(file.is_open()){
file<<"someText\n"; //Write your line to the end of the file.
file.close();
}
//open file again without the ios::app flag
file.open("test.txt");
if(file.is_open()){
for(int i=numLines+1;i>3;i--){ //Move someText\n to line 3
swapLines(file,i-1,i);
}
file.close();
}
return 0;
}
Here's the test.txt file after:
12345678
12345678
someText
12345678
12345678
12345678
I hope this helps!
Based on my basic knowledge of Operating systems, I would say it is not possible.
I mean it is not impossible to make an OS that can allow such functionality with current storage technologies, but doing so would always lead to wastage of space in segments.
But I am not aware of any technology that can allow that. Although some cloud-based DataBases do use such kinds of functionally (like inserting content in middle of a file), but they are made specifically for that DBMS software, with very specifically targeted hardware, and they may also have some custom-built kernels to perform such tasks.

Not able to read whole file

I'm making a C++ program to be able to open a .bmp image and then being able to put it in a 2D array. Right now i have the code like this:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "Image.h"
using namespace std;
struct colour{
int red;
int green;
int blue;
};
Image::Image(string location){
fstream stream;
string tempStr;
stringstream strstr;
stream.open(location);
string completeStr;
while(!stream.eof()){
getline(stream, tempStr);
completeStr.append(tempStr);
}
cout << endl << completeStr;
Image::length = completeStr[0x13]*256 + completeStr[0x12];
Image::width = completeStr[0x17]*256 + completeStr[0x16];
cout << Image::length;
cout << Image::width;
cout << completeStr.length();
int hexInt;
int x = 0x36;
while(x < completeStr.length()){
strstr << noskipws << completeStr[x];
cout << x << ": ";
hexInt = strstr.get();
cout << hex << hexInt << " ";
if((x + 1)%3 == 0){
cout << endl;
}
x++;
}
}
Now if i run this on my test file of 256x256 it will print fine, until it reaches 0x36E where it gives an error / doesn't go further. This happens because the completeStr string doesn't recieve all the data that is in the bmp file. Why isn't able to read all the lines in the bmp file?
There are a number of problems with your code. The principal
one (and probably the reason for your problem) is that you are
opening the file in text mode. Technically, this means that if
the file contains anything but printable characters and a few
specific control characters (like '\t'), you have undefined
behavior. In practice, under Windows, this means that sequences
of 0x0D, 0x0A will be converted into a single '\n', and that
a 0x1A will be interpreted as the end of the file. Not really
what one wants when reading binary data. You should open the
stream in binary mode (std::ios_base::binary).
Not a serious error, but you shouldn't really use an fstream
if you are only going to read the file. In fact, using an
fstream should be very rare: you should use either ifstream
or ofstream. The same thing holds for stringstream (but
I don't see any role for stringstream when reading a binary
file).
Also (and this is a real error), you are using the results of
getline without checking whether is succeeded. The usual
idiom for reading lines would be:
while ( std::getline( source, ling ) ) ...
But like stringstream, you don't want to use getline on
a binary stream; it will remove all of the '\n' (which have
already been mapped from CRLF).
If you want all of the data in memory, the simplest solution is
something like:
std::ifstream source( location.c_str(), std::ios_base::binary );
if ( !source.is_open() ) {
// error handling...
}
std::vector<char> image( (std::istreambuf_iterator<char>( source ) ),
(std::istreambuf_iterator<char>()) );
std::getline reads in a line of text.
It's not useful for a binary file.
Open the file in binary mode and use unformatted input operations (like read).

C++ program to find and replace a string in file

This is a beginner question. I am trying to find a string in text file and replace it back to the same file. Following code works fine collecting contents of file into buffer and replace the string . But when i try to keep the data back to same file, it is filled with some junk character. Please let me know what I am doing wrong ?
#include <iostream>
#include <fstream>
using namespace std;
const char *fileName = "Test.dat";
int main () {
// This is where we'll put the stuff we read from file
char buffer[ 100 ];
ifstream finout(fileName, ios_base::in | ios_base::out | ios_base::binary);
if(!finout.is_open())
{
cout << "Can not open file " << endl;
return(1);
}
while (finout.getline(buffer, sizeof(buffer))
{
string g( buffer );
string search = "am";
string replace = "was";
long j;
if ( (j = g.find(str2)) != string::npos)
{
g.replace(g.find(str2), str2.length(), "str");
finout.write((char *) &g, sizeof(g)); //This won't work
}
cout << g << "\n";
finout.close();
return 0;
}
My Test.dat file contain following information:
Hello, How are you?
I am fine.
When you are read/write as a text file, do not open it by ios_base::binary
You put finout.close(); inside your reading loop, so it just work for one line.
When you are reading/writing a file as a text, use text stream methods and operators.
You are trying to read the size of your string with the sizeof() operator.
This wont work, because it is a keyword, that gives you the non-dynamic size of the object or type.
You should use g.size() to access the string size!
But on the first place, you can handle the stream handle your bug:
finout << g;
will do the job.
First, you want to both read and write a file, so use fstream not ifstream.
Second, you have a text file, so don't use ios_base::binary
Third (char*) &g where g is std::string doesn't work, use g.c_str() instead. (simply write
finout << g;
Now you can start thinking of the implmentation...

end of an Exe file in C++

I am writing a C++ program to read an exe file. I wrote it and I test it on a text file instead of exe file. it was true.
when I test it with an exe file I understand that my exe file have 0x00 value in it (not at its end). so my while loop stop before end of file because I used:
class A{
private:
ifstream myFile;
void Read(char *filename)
};
void A::Read(char *str)
{
myFile.open(str,ios::binary);
while (!myFile.eof())
{
InputFile.get(ch);
myString.push_back(ch);
}
}
what should I do? if I should use size of the file, how can i get it?
You must open the file stream with the std::ios::binary mode flag.
As James McNellis pointed out you need to open the file in binary mode: Try something like the following:
#include <iostream>
#include <fstream>
int main()
{
std::ifstream in("main.obj", std::ios_base::binary);
std::streamsize bytes_read = 0;
if (in.is_open())
{
while (!in.eof())
{
char buf[1024];
// Use unformatted read.
in.read(buf, 1024);
if (in.gcount() > 0)
{
// The first 'in.gcount()' chars in
// 'buf' were read.
bytes_read += in.gcount();
}
}
}
in.close();
std::cout << "bytes read=" << bytes_read << "\n";
return 0;
}
EDIT:
Example modified to use get():
#include <iostream>
#include <fstream>
int main()
{
std::ifstream in("main.obj", std::ios_base::binary);
std::streamsize bytes_read = 0;
if (in.is_open())
{
while (!in.eof())
{
in.get();
if (1 == in.gcount())
{
bytes_read++;
}
}
}
in.close();
std::cout << "bytes read=" << bytes_read << "\n";
return 0;
}
Tested and works correctly.
In addition to opening the file in binary mode, the current code has a subtle bug which will cause the last character in the file to be read twice. The problem is that the myFile.eof() call does not do what you think it does. It does not tell you when you're at the end of the file. It tells you that you have tried to read beyond the end of the file. The idiomatic way to write a read-until-eof loop in C++ is:
while (myFile.get(ch))
myString.push_back(ch);
get returns an istream reference which, in this context, is implicitly convertible to bool and is used to indicate that there is no more data to read.
Only a hunch here, but my suspicion is that you're actually reading the whole file correctly, but measuring it wrong.
File reading (with binary mode) won't stop on a 0-byte, but there are several string related methods that will.
For example, you can't measure the size of a binary "blob" using strlen(), you can't copy it using strcpy().
Without seeing the actual way you're storing and measuring the data, it's hard to see where things go wrong, but I strongly suspect that you're actually reading the whole file correctly if you're using binary mode.
I found my mistake, The program read all the bytes but I cout that bytes in a vector<char>, So it's obvious I saw just bytes before 0x00.