Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Reading in-line data URI into bitmap


This question is answered.


Permlink Replies: 9 - Last Post: Sep 14, 2014 2:21 PM Last Post By: Thomas Grubb
Thomas Grubb

Posts: 61
Registered: 2/27/01
Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 9, 2014 2:41 AM
Hi,
I am parsing an XML file where a node can be a URI. If the URI contains an embedded data image (e.g.,xlink:href="data:image/jpeg;base64,/9j/4AAQSkZ...), How can I read that data into a bitmap?

Thanks,
Tom
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading in-line data URI into bitmap
Correct
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 9, 2014 9:03 AM   in response to: Thomas Grubb in response to: Thomas Grubb
Thomas wrote:

I am parsing an XML file where a node can be a URI. If the URI
contains an embedded data image
(e.g.,xlink:href="data:image/jpeg;base64,/9j/4AAQSkZ...), How
can I read that data into a bitmap?

Strip off the "data:" prefix and split the remaining portion on commas:

image/jpeg
base64
/9j/4AAQSkZ...

The first field is the Content-Type.

The second field is the encoding of the third field.

The third field is the encoded image data.

For example:

uses
  ..., IdGlobal, IdCoderMIME;
 
var
  url, value: string;
  stream: TMemoryStream;
  jpg: TJPEGImage;
begin
  url := ...;
  if TextStartsWith(url, 'data:') then
  begin
    Fetch(url, ':');
    value := Fetch(url, ',');
    if TextIsSame(value, 'image/jpeg') then
    begin
      jpg := TJPEGImage.Create;
      try
        value := Fetch(url, ',');
        if TextIsSame(value, 'base64') then
        begin
          stream := TMemoryStream.Create;
          try
            TIdDecoderMIME.DecodeStream(url, stream);
            stream.Position := 0;
            jpg.LoadFromStream(stream);
          finally
            stream.Free;
          end;
        end;
        // use jpg as needed...
      finally
        jpg.free;
      end;
    end;
  end;
end;


--
Remy Lebeau (TeamB)
Thomas Grubb

Posts: 61
Registered: 2/27/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 10, 2014 5:52 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi Remy,
Thank you so much! This worked great, except the first value := Fetch(url, ','); needed to be value := Fetch(url, ';'); Is this because the specific test case I received is wrong or that sometimes it can be delimited by comma and sometimes by semicolon?

Thanks,
Tom
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading in-line data URI into bitmap
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 10, 2014 11:46 AM   in response to: Thomas Grubb in response to: Thomas Grubb
Thomas wrote:

the first value := Fetch(url, ',');
needed to be value := Fetch(url, ';');

It has been awhile since I last looked at the formal "data:" spec (RFC 2397),
so I thought all of the parameters were comma-separated. They are actually
semicolon-delimited, and then there is a comma between the parameters and
the encoded data, eg:

data:[<mediatype>][;base64],<data>


So, a more accurate implementation would look more like this instead:

uses
  ..., IdGlobal, IdCoderMIME;
 
var
  url, params, param, mediatype, charset, data: string;
  isBase64: boolean;
  stream: TMemoryStream;
  jpg: TJPEGImage;
begin
  url := ...;
 
  if TextStartsWith(url, 'data:') then
  begin
    data := url;
    isBase64 := false;
 
    // remove the 'data:' prefix
    Fetch(data, ':');
 
    // separate the parameters and encoded data
    params := Fetch(data, ',');
 
    // determine media type and parameters
    param := Fetch(params, ';');
    if Pos('/', param) <> 0 then begin
      mediatype = param;
      param := Fetch(params, ';');
    end else begin
      mediatype = 'text/plain';
    end;
    while param <> '' do begin
      if TextIsSame(param, 'base64') then begin
        isBase64 := true;
      end else if TextStartsWith(param, 'charset=') then begin
        charset := Fetch(param, '=');
      end else begin
        ... // handle other parameter as needed
      end;
      param := Fetch(params, ';');
    end;
 
    // process data based on mediatype and parameters ...
    if IsHeaderMediaType(mediatype, 'image/jpeg') and isBase64 then
    begin
      jpg := TJPEGImage.Create;
      try
        stream := TMemoryStream.Create;
        try
          TIdDecoderMIME.DecodeStream(data, stream);
          stream.Position := 0;
          jpg.LoadFromStream(stream);
        finally
          stream.Free;
        end;
        // use jpg as needed...
      finally
        jpg.free;
      end;
    end else begin
      // something else, handle as needed...
    end;
  end;
end;


--
Remy Lebeau (TeamB)
Thomas Grubb

Posts: 61
Registered: 2/27/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 11, 2014 5:21 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thanks! It all works great.
Tom
Thomas Grubb

Posts: 61
Registered: 2/27/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 11, 2014 7:01 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Spoke too soon. When I move the test program from using a string constant to using an attribute from an XML DOM, the code fails. I can see that the XML string length is longer so it is probably some type of encoding problem. Do you have any ideas?
Thanks,
Tom
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 11, 2014 9:24 AM   in response to: Thomas Grubb in response to: Thomas Grubb
Thomas wrote:

Spoke too soon. When I move the test program from using a string
constant to using an attribute from an XML DOM, the code fails. I can
see that the XML string length is longer so it is probably some type
of encoding problem. Do you have any ideas?

Not without seeing the actual attribute and resulting string data.

--
Remy Lebeau (TeamB)
Thomas Grubb

Posts: 61
Registered: 2/27/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 11, 2014 4:14 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
I put the file I am trying to read into Attachments forum:
https://forums.embarcadero.com/thread.jspa?messageID=668199&#668199

Any help is appreciated.

Tom
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 11, 2014 7:29 PM   in response to: Thomas Grubb in response to: Thomas Grubb
Thomas wrote:

I put the file I am trying to read into Attachments forum:
https://forums.embarcadero.com/thread.jspa?messageID=668199&#668199

The problem is that the URL has line breaks in it, which usually get normalized
to #10 when read by an XML reader. TIdDecoderMIME does not ignore line breaks,
thus producing corrupted JPG output. So either remove the line breaks before
calling TIdDecoderMIME.DecodeStream(), or use TIdDecoderMIMELineByLine instead,
feeding it each encoded line one at a time. Either approach produces the
correct JPG image.

Try this:

uses
..., Jpeg, IdGlobal, IdGlobalProtocols, IdCoderMIME;

var
url, params, param, mediatype, charset, data: string;
isBase64: boolean;
stream: TMemoryStream;
jpg: TJPEGImage;
begin
url := ...;

if TextStartsWith(url, 'data:') then
begin
data := url;
isBase64 := false;

// remove the 'data:' prefix
Fetch(data, ':');

// separate the parameters and encoded data
params := Fetch(data, ',');

// determine media type and parameters
param := Fetch(params, ';');
if Pos('/', param) <> 0 then begin
mediatype := param;
param := Fetch(params, ';');
end else begin
mediatype := 'text/plain';
end;

while param <> '' do begin
if TextIsSame(param, 'base64') then begin
isBase64 := true;
end else if TextStartsWith(param, 'charset=') then begin
charset := Fetch(param, '=');
end else begin
// handle other parameter as needed
end;
param := Fetch(params, ';');
end;

// process data based on mediatype and parameters ...
if IsHeaderMediaType(mediatype, 'image/jpeg') and isBase64 then
begin
jpg := TJPEGImage.Create;
try
stream := TMemoryStream.Create;
try
if Pos(#10, data) <> 0 then
begin
with TIdDecoderMIMELineByLine.Create do
try
DecodeBegin(stream);
while data <> '' do
begin
param:= Fetch(data, #10);
// TODO: also check for a trailing #13 and remove it as well...
Decode(param);
end;
DecodeEnd;
finally
Free;
end;
end else begin
TIdDecoderMIME.DecodeStream(data, stream);
end;
stream.Position := 0;
jpg.LoadFromStream(stream);
finally
stream.Free;
end;
// use jpg as needed...
finally
jpg.free;
end;
end else begin
// something else, handle as needed...
end;
end;
end;
{code}

--
Remy Lebeau (TeamB)
Thomas Grubb

Posts: 61
Registered: 2/27/01
Re: Reading in-line data URI into bitmap  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 14, 2014 2:21 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
The problem is that the URL has line breaks in it, which usually get normalized
to #10 when read by an XML reader. TIdDecoderMIME does not ignore line breaks,
thus producing corrupted JPG output. So either remove the line breaks before
Hi Remy,
I figured that was what was happening... I thought maybe there was an option when reading the XML to fix it but I guess not. But this works great! Thank you for your help and patience.
Regards,
Tom
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02