How to create a bitmap/image from Scan0? - c++

I'm having trouble porting the following C# code to C++:
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects
, System.IntPtr buffer, int width, int height)
{
if (isPainting == true)
return;
isPainting = true;
// Save the provided buffer (a bitmap image) as a PNG.
using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, width * 4, System.Drawing.Imaging.PixelFormat.Format32bppRgb, buffer))
{
bitmap.Save(#"LastOnPaint.png", System.Drawing.Imaging.ImageFormat.Png);
} // End Using bitmap
}
What it does:
Create an image from a WebSite/SVG as rendered by the latest version of Chromium embedded and save it as a file.
So this is the corresponding render-handler in C++:
void RenderHandler::OnPaint(
CefRefPtr<CefBrowser> browser,
CefRenderHandler::PaintElementType type,
const CefRenderHandler::RectList& dirtyRects,
const void* buffer, int width, int height
) {
// size_t len = sizeof(buffer) / sizeof(void*);
// printf("buffer length: %zu\n", len); // 1...
// Array size is probably: width*height * 4;
}
So I was looking into what C# does in the bitmap-constructor, which is the following:
public Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0)
{
IntPtr bitmap = IntPtr.Zero;
int status = Gdip.GdipCreateBitmapFromScan0(width, height, stride, unchecked((int)format), new HandleRef(null, scan0), out bitmap);
Gdip.CheckStatus(status);
SetNativeImage(bitmap);
}
internal void SetNativeImage(IntPtr handle) {
if (handle == IntPtr.Zero)
throw new ArgumentException(SR.GetString(SR.NativeHandle0), "handle");
nativeImage = handle;
}
Which traces to
internal const string Gdiplus = "gdiplus.dll";
[DllImport(ExternDll.Gdiplus, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Unicode)] // 3 = Unicode
[ResourceExposure(ResourceScope.Machine)]
internal static extern int GdipCreateBitmapFromScan0(int width, int height, int stride, int format, HandleRef scan0, out IntPtr bitmap);
So I thought I could just call GdipCreateBitmapFromScan0 in gdibitmapflat and be almost finished
GpStatus WINGDIPAPI GdipCreateBitmapFromScan0(INT width
, INT height, INT stride, PixelFormat format
, BYTE* scan0, GpBitmap** bitmap)
So I gathered the necessary header-files for GDI, which was a horrible experience
#ifndef __BITMAPHELPER_H__
#define __BITMAPHELPER_H__
// #define WIN32_LEAN_AND_MEAN
#pragma warning(disable:4458)
#include <Windows.h>
#include <ObjIdl.h>
#include <minmax.h>
#include <gdiplus.h>
#include <wingdi.h>
#include <gdiplusbitmap.h>
#include <gdiplusflat.h>
using namespace Gdiplus;
#pragma comment (lib,"gdiplus.lib")
#pragma warning(default:4458)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <cstdbool>
#include <algorithm>
#include <memory>
And thought this would about do it
#include "BitmapHelper.h"
static void Test()
{
GpBitmap *bitmap = NULL;
GdipCreateBitmapFromScan0(100, 100, 0, PixelFormat32bppARGB, NULL, &bitmap); // create a bitmap object with specified width/height/color
// GpGraphics *graph;
// Image * syntaxTest = NULL;
//syntaxTest->FromFile(TEXT("d:\\abc.jpg"), true); // create an image object
// Bitmap::FromBITMAPINFO
// GpImage *image = NULL;
// Gdiplus::Image()
Bitmap *bmp = NULL;
// GdipLoadImageFromFile(TEXT("d:\\abc.jpg"), &image); // create an image object
// GdipGetImageGraphicsContext(bitmap, &graph); // create a graphic object via bitmap object
// GdipDrawImageI(graph, image, 100, 100); // draw image to this graphic object, it can be done
}
However, it turns out the compiler doesn't know GdipCreateBitmapFromScan0, although it's definitely inside #include <gdiplusflat.h>...
How to create a bitmap/image from Scan0 ?
Note:
While I am at it, I don't want to resort to C++.NET, and ideally not to the WinAPI either; because i'd like it to work on Linux too. And not to a monstrous dependency like SDL either.
So far, it looks like my possible alternatives are using this code:
https://codereview.stackexchange.com/questions/196084/read-and-write-bmp-file-in-c
which means I have to create the bitmap header myselfs.
Or I could use some code from ImageIO.
I can't quite belive that creating a simple bitmap on even a single operating-system is that hard...
Is there really no better (and portable) way to create a simple bitmap from a trivial array of pixel colors ?
And why does the compiler not find GdipCreateBitmapFromScan0 ?
If I had used LoadLibrary and GetProcAddress to invoke it instead of f*ing windows header files, I'd be about finished by now...
And why does #include <gdiplus.h> not include its own dependencies ?

Your looking at the internals of .NET have led you toward using a function that's not part of the documented, public interface of GDI+. It looks to me like that's the real cause of most of your problems.
What I think you probably want to do is start by creating a GdiPlus::Bitmap object from your pixels. It has a constructor that looks like it'll directly accept your data.
Once you've created the Bitmap object, you call its Save member function. Bitmap is publicly derived from Image, so you're basically dealing with the normal Image::Save to generate a PNG.
If you want to eliminate the dependency on Windows code, you might consider using (for one obvious possibility) libpng instead. This gives you quite a lot more control over the process, at the expense of being quite a bit more work to use (depending on what you want to do, probably on the order of a half dozen to a dozen lines of code rather than one or two).

So, after having done this in both GDI+ and raw C, I can safely say that it's actually faster, and not to mention considerably less problematic and less google-intensive just doing the image-handling without GDI/GDI+. Whoever implemented GDI+ has a major brain damage.
Since I haven't yet handled transparency properly, and not yet incorporated lodepng, I've added GDI+ as an optional extra option, for the time being.
// A program to read, write, and crop BMP image files.
#include "Bmp.h"
// Make a copy of a string on the heap.
// - Postcondition: the caller is responsible to free
// the memory for the string.
char *_string_duplicate(const char *string)
{
char *copy = (char*)malloc(sizeof(*copy) * (strlen(string) + 1));
if (copy == NULL)
{
// return "Not enough memory for error message";
const char* error_message = "Not enough memory for error message";
size_t len = strlen(error_message);
char* error = (char*)malloc(len * sizeof(char) + 1);
strcpy(error, error_message);
return error;
}
strcpy(copy, string);
return copy;
}
// Check condition and set error message.
bool _check(bool condition, char **error, const char *error_message)
{
bool is_valid = true;
if (!condition)
{
is_valid = false;
if (*error == NULL) // to avoid memory leaks
{
*error = _string_duplicate(error_message);
}
}
return is_valid;
}
// Write an image to an already open file.
// - Postcondition: it is the caller's responsibility to free the memory
// for the error message.
// - Return: true if and only if the operation succeeded.
bool write_bmp(FILE *fp, BMPImage *image, char **error)
{
// Write header
rewind(fp);
size_t num_read = fwrite(&image->header, sizeof(image->header), 1, fp);
if (!_check(num_read == 1, error, "Cannot write image"))
{
return false;
}
// Write image data
num_read = fwrite(image->data, image->header.image_size_bytes, 1, fp);
if (!_check(num_read == 1, error, "Cannot write image"))
{
return false;
}
return true;
}
// Free all memory referred to by the given BMPImage.
void free_bmp(BMPImage *image)
{
free(image->data);
free(image);
}
// Open file. In case of error, print message and exit.
FILE *_open_file(const char *filename, const char *mode)
{
FILE *fp = fopen(filename, mode);
if (fp == NULL)
{
fprintf(stderr, "Could not open file %s\n", filename);
exit(EXIT_FAILURE);
}
return fp;
}
// Close file and release memory.void _clean_up(FILE *fp, BMPImage *image, char **error)
void _clean_up(FILE *fp, BMPImage *image, char **error)
{
if (fp != NULL)
{
fclose(fp);
}
free_bmp(image);
free(*error);
}
// Print error message and clean up resources.
void _handle_error(char **error, FILE *fp, BMPImage *image)
{
fprintf(stderr, "ERROR: %s\n", *error);
_clean_up(fp, image, error);
exit(EXIT_FAILURE);
}
void write_image(const char *filename, BMPImage *image, char **error)
{
FILE *output_ptr = _open_file(filename, "wb");
if (!write_bmp(output_ptr, image, error))
{
_handle_error(error, output_ptr, image);
}
fflush(output_ptr);
fclose(output_ptr);
_clean_up(output_ptr, image, error);
}
// Return the size of an image row in bytes.
// - Precondition: the header must have the width of the image in pixels.
uint32_t computeImageSize(BMPHeader *bmp_header)
{
uint32_t bytes_per_pixel = bmp_header->bits_per_pixel / BITS_PER_BYTE;
uint32_t bytes_per_row_without_padding = bmp_header->width_px * bytes_per_pixel;
uint32_t padding = (4 - (bmp_header->width_px * bytes_per_pixel) % 4) % 4;
uint32_t row_size_bytes = bytes_per_row_without_padding + padding;
return row_size_bytes * bmp_header->height_px;
}
#ifdef USE_GDI
#pragma warning(disable:4189)
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
} // if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
} // Next j
free(pImageCodecInfo);
return -1; // Failure
}
// https://github.com/lvandeve/lodepng
static bool notInitialized = true;
void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer)
{
// HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (notInitialized)
{
// https://learn.microsoft.com/en-us/windows/desktop/api/gdiplusinit/nf-gdiplusinit-gdiplusstartup
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::Status isOk = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if (isOk != Gdiplus::Status::Ok)
{
printf("Failed on GdiplusStartup\n");
}
notInitialized = false;
// defer
// GdiplusShutdown(gdiplusToken);
} // End if (notInitialized)
// https://learn.microsoft.com/en-us/windows/desktop/gdiplus/-gdiplus-constant-image-pixel-format-constants
Gdiplus::Bitmap* myBitmap = new Gdiplus::Bitmap(width, height, width*4, PixelFormat32bppARGB, (BYTE*)buffer);
// myBitmap->RotateFlip(Gdiplus::Rotate180FlipY);
CLSID pngClsid;
// int result = GetEncoderClsid(L"image/tiff", &tiffClsid);
int result = GetEncoderClsid(L"image/png", &pngClsid);
printf("End GetEncoderClsid:\n");
if (result == -1)
printf("Error: GetEncoderClsid\n");
// throw std::runtime_error("Bitmap::Save");
// if (Ok != myBitmap->Save(L"D\foobartest.png", &pngClsid)) printf("Error: Bitmap::Save");
// WTF ? I guess a standard C/C++-stream would have been too simple ?
IStream* oStream = nullptr;
if (CreateStreamOnHGlobal(NULL, TRUE, (LPSTREAM*)&oStream) != S_OK)
printf("Error on creating an empty IStream\n");
Gdiplus::EncoderParameters encoderParameters;
encoderParameters.Count = 1;
encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality;
encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
encoderParameters.Parameter[0].NumberOfValues = 1;
ULONG quality = 100;
encoderParameters.Parameter[0].Value = &quality;
// https://learn.microsoft.com/en-us/windows/desktop/api/gdiplusheaders/nf-gdiplusheaders-image-save(inistream_inconstclsid_inconstencoderparameters)
if (Gdiplus::Status::Ok != myBitmap->Save(oStream, &pngClsid, &encoderParameters))
printf("Error: Bitmap::Save\n");
// throw std::runtime_error("Bitmap::Save");
ULARGE_INTEGER ulnSize;
LARGE_INTEGER lnOffset;
lnOffset.QuadPart = 0;
oStream->Seek(lnOffset, STREAM_SEEK_END, &ulnSize);
oStream->Seek(lnOffset, STREAM_SEEK_SET, NULL);
uint8_t *pBuff = new uint8_t[(unsigned int)ulnSize.QuadPart];
ULONG ulBytesRead;
oStream->Read(pBuff, (ULONG)ulnSize.QuadPart, &ulBytesRead);
FILE *output_ptr = _open_file(filename, "wb");
fwrite((void*)pBuff, sizeof(uint8_t), (unsigned int)ulnSize.QuadPart, output_ptr);
fflush(output_ptr);
fclose(output_ptr);
oStream->Release();
delete pBuff;
delete myBitmap;
// https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
// std::string rotated_string = base64_encode((const unsigned char*)pBuff, ulnSize.QuadPart);
}
#pragma warning(default:4189)
#else
// TODO: PNG-Encoder
// https://github.com/lvandeve/lodepng
// https://lodev.org/lodepng/
BMPImage * CreateBitmapFromScan0(int32_t w, int32_t h, uint8_t* scan0)
{
BMPImage *new_image = (BMPImage *)malloc(sizeof(*new_image));
BMPHeader *header = (BMPHeader *)malloc(sizeof(*header));
new_image->header = *header;
new_image->header.type = MAGIC_VALUE;
new_image->header.bits_per_pixel = BITS_PER_PIXEL;
new_image->header.width_px = w;
new_image->header.height_px = h;
new_image->header.image_size_bytes = computeImageSize(&new_image->header);
new_image->header.size = BMP_HEADER_SIZE + new_image->header.image_size_bytes;
new_image->header.dib_header_size = DIB_HEADER_SIZE;
new_image->header.offset = (uint32_t) sizeof(BMPHeader);
new_image->header.num_planes = 1;
new_image->header.compression = 0;
new_image->header.reserved1 = 0;
new_image->header.reserved2 = 0;
new_image->header.num_colors = 0;
new_image->header.important_colors = 0;
new_image->header.x_resolution_ppm = 3780; // image->header.x_resolution_ppm;
new_image->header.y_resolution_ppm = 3780; // image->header.y_resolution_ppm;
new_image->data = (uint8_t*)malloc(sizeof(*new_image->data) * new_image->header.image_size_bytes);
memcpy(new_image->data, scan0, new_image->header.image_size_bytes);
return new_image;
}
void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer)
{
BMPImage * image = CreateBitmapFromScan0((int32_t)width, (int32_t)height, (uint8_t*)buffer);
char *error = NULL;
write_image(filename, image, &error);
}
#endif
Header:
#ifndef BITMAPLION_BITMAPINFORMATION_H
#define BITMAPLION_BITMAPINFORMATION_H
#ifdef __cplusplus
// #include <iostream>
// #include <fstream>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#else
#include <stdio.h>
#include <stdlib.h> // for malloc
#include <stdint.h>
#include <stdbool.h>
#include <string.h> // for strlen, strcopy
#endif
#ifdef __linux__
//linux specific code goes here
#elif _WIN32
// windows specific code goes here
#pragma warning(disable:4458)
#include <Windows.h>
#include <ObjIdl.h>
#include <minmax.h>
#include <gdiplus.h>
// #include <gdiplusheaders.h>
// #include <wingdi.h>
// #include <gdiplusbitmap.h>
// #include <gdiplusflat.h>
// #include <Gdipluspixelformats.h>
#pragma comment (lib,"gdiplus.lib")
// using namespace Gdiplus;
#pragma warning(default:4458)
#else
#endif
#define BMP_HEADER_SIZE 54
#define DIB_HEADER_SIZE 40
// Correct values for the header
#define MAGIC_VALUE 0x4D42
#define NUM_PLANE 1
#define COMPRESSION 0
#define NUM_COLORS 0
#define IMPORTANT_COLORS 0
#define BITS_PER_BYTE 8
// #define BITS_PER_PIXEL 24
#define BITS_PER_PIXEL 32
#ifdef _MSC_VER
#pragma pack(push) // save the original data alignment
#pragma pack(1) // Set data alignment to 1 byte boundary
#endif
typedef struct
#ifndef _MSC_VER
__attribute__((packed))
#endif
{
uint16_t type; // Magic identifier: 0x4d42
uint32_t size; // File size in bytes
uint16_t reserved1; // Not used
uint16_t reserved2; // Not used
uint32_t offset; // Offset to image data in bytes from beginning of file
uint32_t dib_header_size; // DIB Header size in bytes
int32_t width_px; // Width of the image
int32_t height_px; // Height of image
uint16_t num_planes; // Number of color planes
uint16_t bits_per_pixel; // Bits per pixel
uint32_t compression; // Compression type
uint32_t image_size_bytes; // Image size in bytes
int32_t x_resolution_ppm; // Pixels per meter
int32_t y_resolution_ppm; // Pixels per meter
uint32_t num_colors; // Number of colors
uint32_t important_colors; // Important colors
} BMPHeader;
#ifdef _MSC_VER
#pragma pack(pop) // restore the previous pack setting
#endif
typedef struct {
BMPHeader header;
// unsigned char* data;
// It is more informative and will force a necessary compiler error
// on a rare machine with 16-bit char.
uint8_t* data;
} BMPImage;
// #define USE_GDI true
#ifndef USE_GDI
BMPImage * CreateBitmapFromScan0(int32_t w, int32_t h, uint8_t* scan0);
#endif
void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer);
#endif //BITMAPLION_BITMAPINFORMATION_H

Related

How to convert CImage to Base64 string in MFC C++

I'm porting some code from C# to C++ with MFC and one thing have stopped me. The original code generated an image and then encoded it as a base64 string to use for en embedded image when generating an HTML file.
The original code first converts it to a byte array
private byte[] AsBytes(System.Drawing.Image image)
{
using (var ms = new System.IO.MemoryStream())
{
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
}
}
The conversion to Base64 is then a simple call, Convert.ToBase64String(pictureAsBytes) for MFC there is Base64Encode while not as nice it appears to do the job. The problem is going from CImage to CByteArray (or something else useful).
The code I have causes a lot of headache, but it looks like
AsBytes(CImage &image, CByteArray &bytes)
{
int pitch = image.GetPitch();
int size = abs(pitch) * image.GetHeight();
const BYTE *src = (BYTE *)image.GetBits();
if(pitch < 0)
{
src -= size;
}
BYTE *pBitmapData = new BYTE[size];
memcpy(pBitmapData, src, size * sizeof(BYTE));
for(int i = 0; i < size; i++)
{
bytes.Add(pBitmapData[i]);
}
}
After some fixes my AsBytes became
bool AsBytes(CImage &image, CByteArray &bytes)
{
IStream *pStream = NULL;
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &pStream);
if( SUCCEEDED(hr) )
{
hr = image.Save(pStream, Gdiplus::ImageFormatPNG);
if( SUCCEEDED(hr) )
{
// Get size
ULARGE_INTEGER liSize;
IStream_Size(pStream, &liSize);
// Assume no huge files
int size = liSize.QuadPart;
BYTE *result = new BYTE[size];
// Set to start
LARGE_INTEGER offset;
offset.HighPart = 0;
offset.LowPart = 0;
offset.QuadPart = 0;
hr = pStream->Seek(offset, STREAM_SEEK_SET, 0);
ULONG read;
hr = pStream->Read(result, size, &read);
if( SUCCEEDED(hr) )
{
bytes.SetSize(read * sizeof(BYTE));
memcpy(bytes.GetData(), result, read * sizeof(BYTE));
}
delete [] result;
pStream->Release();
return true;
}
}
pStream->Release();
return false;
}
It now uses stream to convert it.
To serialize a CImage object into a stream of bytes you can use its CImage::Save overload that accepts an IStream interface, and pass it a memory stream. Since we don't know the resulting size ahead of time we have to make do with a stream that grows as required. SHCreateMemStream can be constructed with defaults for that purpose.
The following implementation serializes a CImage encoded as PNG data into a std::vector<uint8_t>:
#include <atlimage.h>
#include <comdef.h>
#include <Shlwapi.h>
#include <vector>
std::vector<uint8_t> as_bytes(CImage const& img)
{
// Serialize image to memory stream
CComPtr<IStream> stream {};
stream.Attach(::SHCreateMemStream(nullptr, 0));
_com_util::CheckError(img.Save(stream, Gdiplus::ImageFormatPNG));
// Find size in bytes
ULARGE_INTEGER size {};
_com_util::CheckError(stream->Seek({}, STREAM_SEEK_CUR, &size));
if (size.HighPart != 0)
{
throw std::runtime_error("Images larger than 4GiB not supported");
}
// Read memory stream into vector
std::vector<uint8_t> bytes(size.QuadPart);
_com_util::CheckError(stream->Seek({}, STREAM_SEEK_SET, nullptr));
ULONG bytes_read { 0 };
_com_util::CheckError(stream->Read(bytes.data(),
static_cast<ULONG>(bytes.size()),
&bytes_read));
return bytes;
}
The code is using C++ exceptions to report errors, making the function more natural to use. The implementation leaves some room for improvement, in particular the additional allocation of bytes plus the copy. A possible alternative would be to implement the IStream interface with a class that internally writes to a vector directly. This would allow for an implementation without copying data around.
For completeness, here's a base64-encoder that doesn't rely on ATL's implementation. It's using CryptBinaryToStringA instead:
#include <wincrypt.h>
#include <string>
#include <vector>
#pragma comment(lib, "Crypt32.lib")
std::string to_base64(std::vector<uint8_t> const& bytes)
{
// Return empty string on empty input
if (bytes.empty())
{
return {};
}
// Change as desired
auto const flags { CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF };
// Request required character count (including NUL character)
DWORD chars_required {};
if (!::CryptBinaryToStringA(bytes.data(), static_cast<DWORD>(bytes.size()), flags,
nullptr, &chars_required)
|| chars_required < 1)
{
throw std::runtime_error { "CryptBinaryToStringA() failed" };
}
// Create a sufficiently sized string and have the API write into it
std::string base64(chars_required - 1, 0);
if (!::CryptBinaryToStringA(bytes.data(), static_cast<DWORD>(bytes.size()), flags,
base64.data(), &chars_required))
{
throw std::runtime_error { "CryptBinaryToStringA() failed" };
}
return base64;
}
This requires C++17 to compile for the std::string::data() call to return a non-const pointer. Note that overwriting the trailing NUL terminator in the std::string with another NUL terminator is also well defined as of C++<something>.
And with that you have a nice command line utility that base64-encodes images:
int wmain(int argc, wchar_t const* argv[])
{
if (argc != 2)
{
return -1;
}
std::wstring const src { argv[1] };
CImage src_img {};
_com_util::CheckError(src_img.Load(src.c_str()));
auto const bytes = as_bytes(src_img);
auto const base64 = to_base64(bytes);
printf("%s", base64.c_str());
return 0;
}

How to Add 'Comments' to a JPEG File Using MFC

Within the property window of a JPEG image, there is a tab called 'Summary'. Within this tab, there is a field called 'Comments' I would like to write some MFC code which will add a given string to this field e.g "This is a photo".
Does some kind soul out there know how to do this?
Many thanks.
MFC doesn't provide this functionality, however you can use GDI+ for the task. The Image class is capable of reading and writing Exif metadata.
This is almost trivial, and explained under Reading and Writing Metadata. However, since the UserComment metadata tag allows for different character encodings, things get a little more involved. The following code1) implements a command line utility that allows to set (or replace if present) the UserComment field:
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "Gdiplus.lib")
int wmain( int argc, const wchar_t* argv[] ) {
// Input format: AddExifComment "<input filename>" "<comment>" "<output filename>"
if (argc != 4)
return -1;
// Initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken{ 0 };
GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, nullptr );
// Load image
Image* img = new Image(argv[1]);
// Construct Unicode comment
const auto& comment = MakeUnicodeComment(argv[2]);
// Assign the UserComment property
PropertyItem propertyItem;
propertyItem.id = PropertyTagExifUserComment;
propertyItem.length = comment.size();
propertyItem.type = PropertyTagTypeUndefined;
propertyItem.value = (void*)comment.data();
img->SetPropertyItem(&propertyItem);
// Save image
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
img->Save(argv[3], &clsid);
// Cleanup
delete img;
GdiplusShutdown(gdiplusToken);
return 0;
}
Constructing an appropriately formatted comment takes a bit of work. This is implemented in the following function:
#include <vector>
using std::vector;
#include <iterator>
using std::back_inserter;
vector<BYTE> MakeUnicodeComment(const wchar_t* text){
// Exif 2.2 header for Unicode (UCS-2): 'U', 'N', 'I', 'C', 'O', 'D', 'E', '\0'
static const char header[]{"UNICODE"};
static const size_t headerSize{ sizeof(header) / sizeof(header[0]) };
// UserComment field contains the 8-byte header followed by UTF-16LE encoded code units
vector<BYTE> buffer;
std::copy(header, header + headerSize, back_inserter(buffer));
// Append comment text (NUL terminator is not required)
auto current = text;
while (*current) {
buffer.push_back(*current & 0xFF);
buffer.push_back((*current >> 8) & 0xFF);
++current;
}
return buffer;
}
Retrieving the JPEG encoder from its MIME type is copied from Retrieving the Class Identifier for an Encoder for reference:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
1) Error handling elided for brevity.
OK,Finally I manage to solove the problem!There is the code:
OK,Finally I manage to solove the problem!There is the code:
#include "stdafx.h"
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num= 0;
UINT size= 0;
ImageCodecInfo* pImageCodecInfo= NULL;
GetImageEncodersSize(&num, &size);
if(size== 0)
{
return -1;
}
pImageCodecInfo= (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo== NULL)
{
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j=0; j< num; ++j)
{
if(wcscmp(pImageCodecInfo[j].MimeType, format)== 0)
{
*pClsid= pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
// load bitmap from memory,sync way
Bitmap* LoadBitmapFromMemory(const void* memory, DWORD size)
{
Bitmap* bmp = NULL;
IStream* stream = NULL;
if (CreateStreamOnHGlobal(NULL, TRUE, &stream) == S_OK)
{
ULARGE_INTEGER uli;
uli.QuadPart = size;
stream->SetSize(uli);
if (stream->Write(memory, size, NULL) == S_OK)
bmp = new Bitmap(stream);
stream->Release();
}
return bmp;
}
// load bitmap from file,sync way
Bitmap* LoadBitmapFromFile(const TCHAR* file_name)
{
Bitmap* bmp = NULL;
HANDLE file_handle = CreateFile(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle != INVALID_HANDLE_VALUE)
{
DWORD temp = 0;
DWORD file_size = GetFileSize(file_handle, &temp);
if (file_size && !temp) // the file must be less than 4G
{
unsigned char* buffer = new unsigned char[file_size];
if (ReadFile(file_handle, buffer, file_size, &temp, NULL))
bmp = LoadBitmapFromMemory(buffer, temp);
delete [] buffer;
}
CloseHandle(file_handle);
}
return bmp;
}
int _tmain(int argc, _TCHAR* argv[])
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Status stat;
CLSID clsid;
char propertyValue[] = "Fake Photograph";
Bitmap* bitmap = LoadBitmapFromFile(L"E:/sandbox/stone.jpg");
PropertyItem* propertyItem = new PropertyItem;
// Get the CLSID of the JPEG encoder.
GetEncoderClsid(L"image/jpeg", &clsid);
propertyItem->id = PropertyTagCopyright;
propertyItem->length = 16; // string length including NULL terminator
propertyItem->type = PropertyTagTypeASCII;
propertyItem->value = propertyValue;
bitmap->SetPropertyItem(propertyItem);
stat = bitmap->Save(L"E:/sandbox/stone.jpg", &clsid, NULL);
if(stat == Ok)
printf("FakePhoto2.jpg saved successfully.\n");
delete propertyItem;
delete bitmap;
GdiplusShutdown(gdiplusToken);
return 0;
return 0;
}

C/CUDA: Only every fourth element in CudaArray can be indexed

This is my first post, so I am thrilled to get some new insights and enlarge my knowledge. Currently I am working on a C-project where a binary raw file with 3d-data is loaded, processed in CUDA and saved in a new binary raw file.
This is based on the simpleTexture3D project from CUDA Samples:
This is my cpp
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
// includes, cuda
#include <vector_types.h>
#include <driver_functions.h>
#include <cuda_runtime.h>
// CUDA utilities and system includes
#include <helper_cuda.h>
#include <helper_functions.h>
#include <vector_types.h>
typedef unsigned int uint;
typedef unsigned char uchar;
const char *sSDKsample = "simpleTexture3D";
const char *volumeFilename = "Bucky.raw";
const cudaExtent volumeSize = make_cudaExtent(32, 32, 32);
const uint width = 64, height = 64, depth=64;
//const char *volumeFilename = "TestOCT.raw";
//const cudaExtent volumeSize = make_cudaExtent(1024, 512, 512);
//
//const uint width = 1024, height = 512, depth=512;
const dim3 blockSize(8, 8, 8);
const dim3 gridSize(width / blockSize.x, height / blockSize.y, depth / blockSize.z);
uint *d_output = NULL;
int *pArgc = NULL;
char **pArgv = NULL;
extern "C" void cleanup();
extern "C" void initCuda(const uchar *h_volume, cudaExtent volumeSize);
extern "C" void render_kernel(dim3 gridSize, dim3 blockSize, uint *d_output, uint imageW, uint imageH, uint imageD);
void loadVolumeData(char *exec_path);
// render image using CUDA
void render()
{
// call CUDA kernel
render_kernel(gridSize, blockSize, d_output, width, height, depth);
getLastCudaError("render_kernel failed");
}
void cleanup()
{
// cudaDeviceReset causes the driver to clean up all state. While
// not mandatory in normal operation, it is good practice. It is also
// needed to ensure correct operation when the application is being
// profiled. Calling cudaDeviceReset causes all profile data to be
// flushed before the application exits
checkCudaErrors(cudaDeviceReset());
}
// Load raw data from disk
uchar *loadRawFile(const char *filename, size_t size)
{
FILE *fp = fopen(filename, "rb");
if (!fp)
{
fprintf(stderr, "Error opening file '%s'\n", filename);
return 0;
}
uchar *data = (uchar *) malloc(size);
size_t read = fread(data, 1, size, fp);
fclose(fp);
printf("Read '%s', %lu bytes\n", filename, read);
return data;
}
// write raw data to disk
int writeRawFile(const char *filename, uchar *data, size_t size)
{
int returnState=0;
// cut file extension from filename
char *a=strdup(filename); //via strdup you dumb a const char to char, you must free it yourself
int len = strlen(a);
a[len-4] = '\0'; //deletes '.raw'
//printf("%s\n",a);
char b[50];
sprintf(b, "_%dx%dx%d_out.raw", width, height, depth);
//char b[]="_out.raw"; //Add suffix out to filename
char buffer[256]; // <- danger, only storage for 256 characters.
strncpy(buffer, a, sizeof(buffer));
strncat(buffer, b, sizeof(buffer));
free(a);
FILE *fp = fopen(buffer, "wb"); //Open or create file for writing as binary, all existing data is cleared
if (!fp)
{
fprintf(stderr, "Error opening or creating file '%s'\n", buffer);
return 0;
}
size_t write = fwrite(data, 1, size, fp);
fclose(fp);
if (write==size)
{
printf("Wrote %lu bytes to '%s'\n", write, buffer);
return 0;
}
else
{
printf("Error writing data to file '%s'\n", buffer);
return 1;
}
}
// General initialization call for CUDA Device
int chooseCudaDevice(int argc, char **argv)
{
int result = 0;
result = findCudaDevice(argc, (const char **)argv);
return result;
}
void runAutoTest(char *exec_path, char *PathToFile)
{
// set path
char *path;
if (PathToFile == NULL)
{
path = sdkFindFilePath(volumeFilename, exec_path);
}
else
{
path = PathToFile;
}
if (path == NULL)
{
fprintf(stderr, "Error unable to find 3D Volume file: '%s'\n", volumeFilename);
exit(EXIT_FAILURE);
}
// Allocate output memory
checkCudaErrors(cudaMalloc((void **)&d_output, width*height*depth*sizeof(uchar)));
// zero out the output array with cudaMemset
cudaMemset(d_output, 0, width*height*depth*sizeof(uchar));
// render the volumeData
render_kernel(gridSize, blockSize, d_output, width, height, depth);
checkCudaErrors(cudaDeviceSynchronize());
getLastCudaError("render_kernel failed");
uchar *h_output = (uchar*)malloc(width*height*depth);
checkCudaErrors(cudaMemcpy(h_output, d_output, width*height*depth*sizeof(uchar), cudaMemcpyDeviceToHost));
int wState=writeRawFile(path,h_output,width*height*depth);
checkCudaErrors(cudaFree(d_output));
free(h_output);
// cudaDeviceReset causes the driver to clean up all state. While
// not mandatory in normal operation, it is good practice. It is also
// needed to ensure correct operation when the application is being
// profiled. Calling cudaDeviceReset causes all profile data to be
// flushed before the application exits
cudaDeviceReset();
//exit(bTestResult ? EXIT_SUCCESS : EXIT_FAILURE);
}
void loadVolumeData(char *exec_path, char *PathToFile)
{
char *path;
// load volume data
if (PathToFile == NULL)
{
path = sdkFindFilePath(volumeFilename, exec_path);
}
else
{
path = PathToFile;
}
if (path == NULL)
{
fprintf(stderr, "Error unable to find 3D Volume file: '%s'\n", volumeFilename);
exit(EXIT_FAILURE);
}
size_t size = volumeSize.width*volumeSize.height*volumeSize.depth;
uchar *h_volume = loadRawFile(path, size);
//int wState=writeRawFile(path,h_volume,size);
initCuda(h_volume, volumeSize);
free(h_volume);
}
////////////////////////////////////////////////////////////////////////////////
// Program main
////////////////////////////////////////////////////////////////////////////////
int
main(int argc, char **argv)
{
pArgc = &argc;
pArgv = argv;
char *image_file = NULL;
printf("%s Starting...\n\n", sSDKsample);
if (checkCmdLineFlag(argc, (const char **)argv, "file")) //Note cmd line argument is -file "PathToFile/File.raw"
{ // for example -file "C:\ProgramData\NVIDIA Corporation\CUDA Samples\v7.0\2_Graphics\simpleTexture3D_FanBeamCorr\data\TestOCT_Kopie.raw"
getCmdLineArgumentString(argc, (const char **)argv, "file", &image_file);
}
if (image_file)
{
chooseCudaDevice(argc, argv);
loadVolumeData(argv[0],image_file);
runAutoTest(argv[0],image_file);
}
else
{
// use command-line specified CUDA device, otherwise use device with highest Gflops/s
chooseCudaDevice(argc, argv);
loadVolumeData(argv[0],NULL);
runAutoTest(argv[0],NULL);
}
printf("I am finished...\n"
"Can I get some ice cream please\n");
exit(EXIT_SUCCESS);
}
And this is my .cu
#ifndef _SIMPLETEXTURE3D_KERNEL_CU_
#define _SIMPLETEXTURE3D_KERNEL_CU_
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <helper_cuda.h>
#include <helper_math.h>
typedef unsigned int uint;
typedef unsigned char uchar;
texture<uchar, 3, cudaReadModeNormalizedFloat> tex; // 3D texture
cudaArray *d_volumeArray = 0;
__global__ void
d_render(uint *d_output, uint imageW, uint imageH, uint imageD)
{
uint x = __umul24(blockIdx.x, blockDim.x) + threadIdx.x;
uint y = __umul24(blockIdx.y, blockDim.y) + threadIdx.y;
uint z = __umul24(blockIdx.z, blockDim.z) + threadIdx.z;
// float u = x / (float) imageW;
// float v = y / (float) imageH;
//float w = z / (float) imageD;
// // read from 3D texture
// float voxel = tex3D(tex, u, v, w);
uint ps=__umul24(imageW,imageH);
if ((x < imageW) && (y < imageH) && (z < imageD))
{
// write output color
uint i = __umul24(z,ps) +__umul24(y, imageW) + x;
d_output[1] = (uchar) 255;//+0*voxel*255;
}
}
extern "C"
void initCuda(const uchar *h_volume, cudaExtent volumeSize)
{
// create 3D array
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<uchar>();
checkCudaErrors(cudaMalloc3DArray(&d_volumeArray, &channelDesc, volumeSize));
// copy data to 3D array
cudaMemcpy3DParms copyParams = {0};
copyParams.srcPtr = make_cudaPitchedPtr((void *)h_volume, volumeSize.width*sizeof(uchar), volumeSize.width, volumeSize.height);
copyParams.dstArray = d_volumeArray;
copyParams.extent = volumeSize;
copyParams.kind = cudaMemcpyHostToDevice;
checkCudaErrors(cudaMemcpy3D(&copyParams));
// set texture parameters
tex.normalized = true; // access with normalized texture coordinates
tex.filterMode = cudaFilterModeLinear; // linear interpolation
tex.addressMode[0] = cudaAddressModeBorder; // wrap texture coordinates
tex.addressMode[1] = cudaAddressModeBorder;
tex.addressMode[2] = cudaAddressModeBorder;
// bind array to 3D texture
checkCudaErrors(cudaBindTextureToArray(tex, d_volumeArray, channelDesc));
}
extern "C"
void render_kernel(dim3 gridSize, dim3 blockSize, uint *d_output, uint imageW, uint imageH, uint imageD)
{
d_render<<<gridSize, blockSize>>>(d_output, imageW, imageH, imageD);
}
#endif // #ifndef _SIMPLETEXTURE3D_KERNEL_CU_
As you can see, currently, I set all values to zero except the index = 1, which is set to 255. Yet when I now open the image stack in Fiji, I see that the fourth pixel on the first slide is white. If I use index=i instead, I get white vertical lines across the image stack periodically every four columns. Generally spoken, it seems that only every fourth element is beeing indexed in the CudaArray. So I am wondering if there is somekind of error here resulting from sizeof(uchar)=1 and sizeof(uint)=4. There would obviously be the factor 4 :)
I am eager to here from you experts
Cheers Mika
I figured it out by myself. The kernel works with uint* d_output while the copy to the host is written into a uchar* h_output
uchar *h_output = (uchar*)malloc(width*height*depth);
checkCudaErrors(cudaMemcpy(h_output, d_output, width*height*depth*sizeof(uchar), cudaMemcpyDeviceToHost));
This led to this strange behavior

AccessViolationException when using C++ DLL from C#

I have a C++ DLL for use from C#. I have a function which takes a string passed to it, and I have those set on the C++ function parameters as const char * like so:
int __stdcall extract_all_frames(const char* szDestination, float scaleFactor)
The main body of this function is copied directly from a working FFmpeg example function so I'm almost certain the problem isn't there. I feel like the problem is in this modification I made to it:
//Open file
char szFilename[32];
sprintf_s(szFilename, sizeof(szFilename), "frame%d.ppm\0", iFrame);
// JOIN szFILENAME AND szDESTINATION
std::string buffer(szDestination, sizeof(szDestination));
buffer.append("\\");
buffer.append(szDestination);
Which is supposed to be a concatenated path and directory. I then pass buffer.c_str() into fopen_s(), which takes const char * not std::string. Whenever calling this function from C#, I get the following exception:
A first chance exception of type 'System.AccessViolationException' occurred in XRF FFmpeg Invoke Test.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
This is the complete code:
#include "stdafx.h"
#pragma comment (lib, "avcodec.lib")
#pragma comment (lib, "avformat.lib")
#pragma comment (lib, "avutil.lib")
#pragma comment (lib, "swscale.lib")
extern "C"
{
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libavutil\avutil.h>
#include <libswscale\swscale.h>
}
#include <string>
#include "Xrf.FFmpeg.hpp"
void save_frame(AVFrame* pFrame, int iFrame, const char* szDestination)
{
//Open file
char szFilename[32];
sprintf_s(szFilename, sizeof(szFilename), "frame%d.ppm\0", iFrame);
// JOIN szFILENAME AND szDESTINATION
std::string buffer(szDestination, sizeof(szDestination));
buffer.append("\\");
buffer.append(szDestination);
FILE* pFile;
errno_t openError = fopen_s(&pFile, buffer.c_str(), "wb");
if (pFile == NULL)
{
return;
}
//Write header
int width = pFrame->width;
int height = pFrame->height;
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
//Write pixel data
for (int y = 0; y < height; y++)
{
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
}
// Close file
fclose(pFile);
}
int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)
{
// Check if scaleFactor is valid
if ((scaleFactor != 0.f) &&
(scaleFactor > 3.f))
{
fprintf(stderr, "Xrf: Scale factor '%f' out of bounds!\nMust be greater than 0, and less then or equal to 3.0.\n", scaleFactor);
return -1;
}
// Register all formats and codecs
av_register_all();
AVFormatContext* pFormatCtx;
if (avformat_open_input(&pFormatCtx, szPath, nullptr, nullptr) != 0)
{
fprintf(stderr, "libavformat: Couldn't open file '%s'!\n", szPath);
return -1;
}
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
fprintf(stderr, "libavformat: Unable to find stream information!\n");
return -1;
}
// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, szPath, 0);
// Find the first video stream
size_t i;
int videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
break;
}
}
if (videoStream == -1)
{
fprintf(stderr, "libavformat: No video stream found!\n");
return -1;
}
// Get a pointer to the codec context for the video stream
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoStream]->codec;
// Scale the frame
int scaleHeight = static_cast<int>(floor(pCodecCtx->height * scaleFactor));
int scaleWidth = static_cast<int>(floor(pCodecCtx->width * scaleFactor));
//Check if frame sizes are valid (not 0, because that's dumb)
if (scaleHeight == 0 || scaleWidth == 0)
{
fprintf(stderr, "Xrf: Scale factor caused a zero value in either width or height!\n");
return -1;
}
// Find the decoder for the video stream
AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
{
fprintf(stderr, "libavcodec: Unsupported codec!\n");
return -1; // Codec not found
}
// Open codec
AVDictionary* optionsDict = nullptr;
if (avcodec_open2(pCodecCtx, pCodec, &optionsDict) < 0)
{
fprintf(stderr, "libavcodec: Couldn't open codec '%s'!\n", pCodec->long_name);
return -1;
}
// Allocate video frame
AVFrame* pFrame = av_frame_alloc();
// Allocate an AVFrame structure
AVFrame* pFrameRGB = av_frame_alloc();
if (pFrameRGB == NULL)
{
fprintf(stderr, "libavformat: Unable to allocate a YUV->RGB resampling AVFrame!\n");
return -1;
}
// Determine required buffer size and allocate buffer
int numBytes = avpicture_get_size(PIX_FMT_RGB24, scaleWidth, scaleHeight);
uint8_t* buffer = static_cast <uint8_t *> (av_malloc(numBytes * sizeof(uint8_t)));
struct SwsContext* sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
scaleWidth,
scaleHeight,
PIX_FMT_RGB24,
SWS_BILINEAR,
nullptr, nullptr, nullptr);
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill(reinterpret_cast <AVPicture *> (pFrameRGB),
buffer,
PIX_FMT_RGB24,
scaleWidth,
scaleHeight);
// Read frames and save first five frames to disk
AVPacket packet;
int frameFinished;
while (av_read_frame(pFormatCtx, &packet) >= 0)
{
// Is this a packet from the video stream?
if (packet.stream_index == videoStream)
{
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if (frameFinished)
{
// Convert the image from its native format to RGB
sws_scale(sws_ctx,
static_cast <uint8_t const * const *> (pFrame->data),
pFrame->linesize,
0,
pCodecCtx->height,
pFrameRGB->data,
pFrameRGB->linesize);
// Save the frame to disk
if (++i <= 5)
{
save_frame(pFrameRGB, i, szDestination);
}
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
av_free(buffer); // Free the RGB image
av_free(pFrameRGB);
av_free(pFrame); // Free the YUV frame
avcodec_close(pCodecCtx); // Close the codec
avformat_close_input(&pFormatCtx); // Close the video file
return 0;
}
I don't know if the error is in my modification (most likely, I'm extremely new to C++), or the other code, as the exception only throws on the invocation line in C#, not the actual C++ line causing the problem.
This is wrong:
std::string buffer(szDestination, sizeof(szDestination));
szDestination is a pointer, thus sizeof(szDestination) will return the pointer size, in bytes, not the number of characters.
If szDestination is a null terminated string, use strlen or similar function to determine the number of characters. If it isn't null terminated, then you need to pass the number of bytes to copy as a parameter.
The better thing to do is when your DLL function is called:
int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)
take those pointers and immediately assign them to std::string. Then drop all usage of char* or const char* from there. There is no need for your helper functions to deal with "dumb" character pointers.
Example:
int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)
{
std::string sPath = szPath;
std::string sDestination = sDestination;
// From here, use sPath and sDestination
//...
}
// redefinition of save_frame
//...
void save_frame(AVFrame* pFrame, int iFrame, const std::string& szDestination)
{
//Open file
std::string buffer = "frame" + to_string(iFrame) + ".ppm\0";
buffer.append("\\");
buffer.append(szDestination);
//...
}

Espeak SAPI/dll usage on Windows?

Question: I am trying to use the espeak text-to-speech engine.
So for I got it working wounderfully on linux (code below).
Now I wanted to port this basic program to windows, too, but it's nearly impossible...
Part of the problem is that the windows dll only allows for AUDIO_OUTPUT_SYNCHRONOUS, which means it requires a callback, but I can't figure out how to play the audio from the callback... First it crashed, then I realized, I need a callback function, now I get the data in the callback function, but I don't know how to play it... as it is neither a wav file nor plays automatically as on Linux.
The sourceforge site is rather useless, because it basically says use the SAPI version, but then there is no example on how to use the sapi espeak dll...
Anyway, here's my code, can anybody help?
#ifdef __cplusplus
#include <cstdio>
#include <cstdlib>
#include <cstring>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <assert.h>
#include <ctype.h>
//#include "speak_lib.h"
#include "espeak/speak_lib.h"
// libespeak-dev: /usr/include/espeak/speak_lib.h
// apt-get install libespeak-dev
// apt-get install libportaudio-dev
// g++ -o mine mine.cpp -lespeak
// g++ -o mine mine.cpp -I/usr/include/espeak/ -lespeak
// gcc -o mine mine.cpp -I/usr/include/espeak/ -lespeak
char voicename[40];
int samplerate;
int quiet = 0;
static char genders[4] = {' ','M','F',' '};
//const char *data_path = "/usr/share/"; // /usr/share/espeak-data/
const char *data_path = NULL; // use default path for espeak-data
int strrcmp(const char *s, const char *sub)
{
int slen = strlen(s);
int sublen = strlen(sub);
return memcmp(s + slen - sublen, sub, sublen);
}
char * strrcpy(char *dest, const char *source)
{
// Pre assertions
assert(dest != NULL);
assert(source != NULL);
assert(dest != source);
// tk: parentheses
while((*dest++ = *source++))
;
return(--dest);
}
const char* GetLanguageVoiceName(const char* pszShortSign)
{
#define LANGUAGE_LENGTH 30
static char szReturnValue[LANGUAGE_LENGTH] ;
memset(szReturnValue, 0, LANGUAGE_LENGTH);
for (int i = 0; pszShortSign[i] != '\0'; ++i)
szReturnValue[i] = (char) tolower(pszShortSign[i]);
const espeak_VOICE **voices;
espeak_VOICE voice_select;
voices = espeak_ListVoices(NULL);
const espeak_VOICE *v;
for(int ix=0; (v = voices[ix]) != NULL; ix++)
{
if( !strrcmp( v->languages, szReturnValue) )
{
strcpy(szReturnValue, v->name);
return szReturnValue;
}
} // End for
strcpy(szReturnValue, "default");
return szReturnValue;
} // End function getvoicename
void ListVoices()
{
const espeak_VOICE **voices;
espeak_VOICE voice_select;
voices = espeak_ListVoices(NULL);
const espeak_VOICE *v;
for(int ix=0; (v = voices[ix]) != NULL; ix++)
{
printf("Shortsign: %s\n", v->languages);
printf("age: %d\n", v->age);
printf("gender: %c\n", genders[v->gender]);
printf("name: %s\n", v->name);
printf("\n\n");
} // End for
} // End function getvoicename
int main()
{
printf("Hello World!\n");
const char* szVersionInfo = espeak_Info(NULL);
printf("Espeak version: %s\n", szVersionInfo);
samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK,0,data_path,0);
strcpy(voicename, "default");
// espeak --voices
strcpy(voicename, "german");
strcpy(voicename, GetLanguageVoiceName("DE"));
if(espeak_SetVoiceByName(voicename) != EE_OK)
{
printf("Espeak setvoice error...\n");
}
static char word[200] = "Hello World" ;
strcpy(word, "TV-fäns aufgepasst, es ist 20 Uhr 15. Zeit für Rambo 3");
strcpy(word, "Unnamed Player wurde zum Opfer von GSG9");
int speed = 220;
int volume = 500; // volume in range 0-100 0=silence
int pitch = 50; // base pitch, range 0-100. 50=normal
// espeak.cpp 625
espeak_SetParameter(espeakRATE, speed, 0);
espeak_SetParameter(espeakVOLUME,volume,0);
espeak_SetParameter(espeakPITCH,pitch,0);
// espeakRANGE: pitch range, range 0-100. 0-monotone, 50=normal
// espeakPUNCTUATION: which punctuation characters to announce:
// value in espeak_PUNCT_TYPE (none, all, some),
espeak_VOICE *voice_spec = espeak_GetCurrentVoice();
voice_spec->gender=2; // 0=none 1=male, 2=female,
//voice_spec->age = age;
espeak_SetVoiceByProperties(voice_spec);
espeak_Synth( (char*) word, strlen(word)+1, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL);
espeak_Synchronize();
strcpy(voicename, GetLanguageVoiceName("EN"));
espeak_SetVoiceByName(voicename);
strcpy(word, "Geany was fragged by GSG9 Googlebot");
strcpy(word, "Googlebot");
espeak_Synth( (char*) word, strlen(word)+1, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL);
espeak_Synchronize();
espeak_Terminate();
printf("Espeak terminated\n");
return EXIT_SUCCESS;
}
/*
if(espeak_SetVoiceByName(voicename) != EE_OK)
{
memset(&voice_select,0,sizeof(voice_select));
voice_select.languages = voicename;
if(espeak_SetVoiceByProperties(&voice_select) != EE_OK)
{
fprintf(stderr,"%svoice '%s'\n",err_load,voicename);
exit(2);
}
}
*/
The above code is for Linux.
The below code is about as far as I got on Vista x64 (32 bit emu):
#ifdef __cplusplus
#include <cstdio>
#include <cstdlib>
#include <cstring>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <assert.h>
#include <ctype.h>
#include "speak_lib.h"
//#include "espeak/speak_lib.h"
// libespeak-dev: /usr/include/espeak/speak_lib.h
// apt-get install libespeak-dev
// apt-get install libportaudio-dev
// g++ -o mine mine.cpp -lespeak
// g++ -o mine mine.cpp -I/usr/include/espeak/ -lespeak
// gcc -o mine mine.cpp -I/usr/include/espeak/ -lespeak
char voicename[40];
int iSampleRate;
int quiet = 0;
static char genders[4] = {' ','M','F',' '};
//const char *data_path = "/usr/share/"; // /usr/share/espeak-data/
//const char *data_path = NULL; // use default path for espeak-data
const char *data_path = "C:\\Users\\Username\\Desktop\\espeak-1.43-source\\espeak-1.43-source\\";
int strrcmp(const char *s, const char *sub)
{
int slen = strlen(s);
int sublen = strlen(sub);
return memcmp(s + slen - sublen, sub, sublen);
}
char * strrcpy(char *dest, const char *source)
{
// Pre assertions
assert(dest != NULL);
assert(source != NULL);
assert(dest != source);
// tk: parentheses
while((*dest++ = *source++))
;
return(--dest);
}
const char* GetLanguageVoiceName(const char* pszShortSign)
{
#define LANGUAGE_LENGTH 30
static char szReturnValue[LANGUAGE_LENGTH] ;
memset(szReturnValue, 0, LANGUAGE_LENGTH);
for (int i = 0; pszShortSign[i] != '\0'; ++i)
szReturnValue[i] = (char) tolower(pszShortSign[i]);
const espeak_VOICE **voices;
espeak_VOICE voice_select;
voices = espeak_ListVoices(NULL);
const espeak_VOICE *v;
for(int ix=0; (v = voices[ix]) != NULL; ix++)
{
if( !strrcmp( v->languages, szReturnValue) )
{
strcpy(szReturnValue, v->name);
return szReturnValue;
}
} // End for
strcpy(szReturnValue, "default");
return szReturnValue;
} // End function getvoicename
void ListVoices()
{
const espeak_VOICE **voices;
espeak_VOICE voice_select;
voices = espeak_ListVoices(NULL);
const espeak_VOICE *v;
for(int ix=0; (v = voices[ix]) != NULL; ix++)
{
printf("Shortsign: %s\n", v->languages);
printf("age: %d\n", v->age);
printf("gender: %c\n", genders[v->gender]);
printf("name: %s\n", v->name);
printf("\n\n");
} // End for
} // End function getvoicename
/* Callback from espeak. Directly speaks using AudioTrack. */
#define LOGI(x) printf("%s\n", x)
static int AndroidEspeakDirectSpeechCallback(short *wav, int numsamples, espeak_EVENT *events)
{
char buf[100];
sprintf(buf, "AndroidEspeakDirectSpeechCallback: %d samples", numsamples);
LOGI(buf);
if (wav == NULL)
{
LOGI("Null: speech has completed");
}
if (numsamples > 0)
{
//audout->write(wav, sizeof(short) * numsamples);
sprintf(buf, "AudioTrack wrote: %d bytes", sizeof(short) * numsamples);
LOGI(buf);
}
return 0; // continue synthesis (1 is to abort)
}
static int AndroidEspeakSynthToFileCallback(short *wav, int numsamples,espeak_EVENT *events)
{
char buf[100];
sprintf(buf, "AndroidEspeakSynthToFileCallback: %d samples", numsamples);
LOGI(buf);
if (wav == NULL)
{
LOGI("Null: speech has completed");
}
// The user data should contain the file pointer of the file to write to
//void* user_data = events->user_data;
FILE* user_data = fopen ( "myfile1.wav" , "ab" );
FILE* fp = static_cast<FILE *>(user_data);
// Write all of the samples
fwrite(wav, sizeof(short), numsamples, fp);
return 0; // continue synthesis (1 is to abort)
}
int main()
{
printf("Hello World!\n");
const char* szVersionInfo = espeak_Info(NULL);
printf("Espeak version: %s\n", szVersionInfo);
iSampleRate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 4096, data_path, 0);
if (iSampleRate <= 0)
{
printf("Unable to initialize espeak");
return EXIT_FAILURE;
}
//samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK,0,data_path,0);
//ListVoices();
strcpy(voicename, "default");
// espeak --voices
//strcpy(voicename, "german");
//strcpy(voicename, GetLanguageVoiceName("DE"));
if(espeak_SetVoiceByName(voicename) != EE_OK)
{
printf("Espeak setvoice error...\n");
}
static char word[200] = "Hello World" ;
strcpy(word, "TV-fäns aufgepasst, es ist 20 Uhr 15. Zeit für Rambo 3");
strcpy(word, "Unnamed Player wurde zum Opfer von GSG9");
int speed = 220;
int volume = 500; // volume in range 0-100 0=silence
int pitch = 50; // base pitch, range 0-100. 50=normal
// espeak.cpp 625
espeak_SetParameter(espeakRATE, speed, 0);
espeak_SetParameter(espeakVOLUME,volume,0);
espeak_SetParameter(espeakPITCH,pitch,0);
// espeakRANGE: pitch range, range 0-100. 0-monotone, 50=normal
// espeakPUNCTUATION: which punctuation characters to announce:
// value in espeak_PUNCT_TYPE (none, all, some),
//espeak_VOICE *voice_spec = espeak_GetCurrentVoice();
//voice_spec->gender=2; // 0=none 1=male, 2=female,
//voice_spec->age = age;
//espeak_SetVoiceByProperties(voice_spec);
//espeak_SetSynthCallback(AndroidEspeakDirectSpeechCallback);
espeak_SetSynthCallback(AndroidEspeakSynthToFileCallback);
unsigned int unique_identifier;
espeak_ERROR err = espeak_Synth( (char*) word, strlen(word)+1, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, &unique_identifier, NULL);
err = espeak_Synchronize();
/*
strcpy(voicename, GetLanguageVoiceName("EN"));
espeak_SetVoiceByName(voicename);
strcpy(word, "Geany was fragged by GSG9 Googlebot");
strcpy(word, "Googlebot");
espeak_Synth( (char*) word, strlen(word)+1, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL);
espeak_Synchronize();
*/
// espeak_Cancel();
espeak_Terminate();
printf("Espeak terminated\n");
system("pause");
return EXIT_SUCCESS;
}
Have you tried passing the buffer you obtain in your callback to sndplaysnd()??
Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long
Its standard winAPI is as follows:
sndPlaySound(buffer[0], SND_ASYNC | SND_MEMORY)
Alternately, if you have a wav-file that has the audio to play:
sndPlaySound(filename, SND_ASYNC)
playsound has a ASYNC mode that wouldn't block your program's execution while the audio is being played.
NOTE: I have used it in VB and the above snippets are for use in VB. If you are coding in VC++, you might have to modify them accordingly. But the basic intention remains the same; to pass the buffer to sndPlaySound with the ASYNC flag set.
Good LUCK!!
Several changes in source code are needed to make the windows library have the same functionality as the one on Linux. I listed the changes here. The ready to use binary is also available.
All the patches and the description were also sent to espeak maintainer (publicly, through the mailing list and patches tracker), so maybe in future it will be available directly.