Recent

Author Topic: A little bug in fpsimages.pas  (Read 1340 times)

Roni Wolf

  • New member
  • *
  • Posts: 8
A little bug in fpsimages.pas
« on: May 07, 2025, 10:41:51 pm »
Hi!
First post here! (sorry if my english is not good, my primary language is portuguese).

I'm working on a project that uses FPSpreadsheet to read a spreadsheet with several embedded figures. I encountered an issue where some JPEG images were not being recognized correctly, leading to "Image format not supported" warnings.

After debugging the fpsimages.pas unit, specifically the image type identification and metadata reading functions, I identified two main areas for improvement:

Handling of different APPn segments in JPEG files: The original code had a strict size check after reading the first APP0 segment (FF E0). If subsequent APPn segments (FF Ex) had a different size than expected for a standard APP0, the process of reading image dimensions (width and height) would terminate prematurely. This prevented the code from reaching the Start Of Frame (SOF) marker where the dimensions are typically stored.

Proposed Solution: I modified the size check for APP0 segments to be less restrictive, ensuring that the code continues to scan for other segments, including the SOF marker (FF C0 to FF C3), even if other APPn segments with different sizes are encountered. This allows for correct extraction of dimensions from a wider range of JPEG files.

Resetting DPI in GetTIFSize function: The GetTIFSize function, used to read metadata from TIFF images (and also called within the JPEG parsing logic for EXIF blocks), was initializing the DPI (dots per inch) values (dpiX, dpiY) to zero. If the EXIF block in a JPEG file did not contain DPI information, or if there was an error reading it, the DPI would be incorrectly reported as zero, even if DPI information was present in the APP0 segment.

Proposed Solution: I modified GetTIFSize to store the initial DPI values passed to it and only reset them to a default "invalid" value (-1 in my implementation) internally. If the function fails to read DPI from the TIFF/EXIF data, it restores the original DPI values passed to it. This ensures that if DPI is available in the APP0 segment of a JPEG, it is not overwritten by a failed DPI read from the EXIF block.

Outcome:

With these changes, FPSpreadsheet is now able to correctly identify and extract dimensions and DPI from a broader range of JPEG images embedded in XLSX files that were previously causing "Image format not supported" errors.

I believe these improvements would enhance the robustness of the image handling capabilities in FPSpreadsheet. I would like to suggest incorporating these changes into the library. I can provide the modified code snippets above:

In GetJPGSize:
Code: Pascal  [Select][+][-]
  1.   while (AStream.Position < AStream.Size) and (rec.Marker = $FF) do begin
  2.     if AStream.Read(rec, SizeOf(rec)) < SizeOf(rec) then exit;
  3.     rec.RecSize := BEToN(rec.RecSize);
  4.     p := AStream.Position - 2;
  5.     case rec.RecType of
  6.       $E0:  // APP0 record
  7.         if (rec.RecSize >= SizeOf(TAPP0Record) - 2) then // -2 because RecSize includes the 2 bytes of the size itself
  8.         begin
  9.           AStream.Read(app0{%H-}, SizeOf(app0));
  10.           if stricomp(pchar(app0.JFIF), 'JFIF') = 0 then
  11.           begin
  12.             dpiX := BEToN(app0.XDensity);
  13.             dpiY := BEToN(app0.YDensity);
  14.             u := app0.Units;
  15.           end;
  16.         end;
  17.       $E1:   // APP1 record (EXIF)
  18.  
  19. ...
  20.  
  21.  

In GetTIFSize:

Code: Pascal  [Select][+][-]
  1. var
  2.   header: TTifHeader = (BOM:0; Sig:0; IFD:0);
  3.   dirEntries: Word;
  4.   field: TIFD_Field = (Tag:0; FieldType:0; ValCount:0; ValOffset:0);
  5.   i: Integer;
  6.   bo: TByteOrder;
  7.   num, denom: LongInt;
  8.   units: Word;
  9.   p, pStart: Int64;
  10.   initialDPIX: Double; // Local variable to store the initial value of dpiX
  11.   initialDPIY: Double; // Local variable to store the initial value of dpiY
  12.  
  13. begin
  14.   Result := false;
  15.   AWidth := 0;
  16.   AHeight := 0;
  17.   //dpiX := 0;
  18.   //dpiY := 0;
  19.   units := 0;
  20.  
  21.   initialDPIX := dpiX; // Saves passed DPI values
  22.   initialDPIY := dpiY;
  23.  
  24.   dpiX := -1; // Set to "invalid" for internal tracking
  25.   dpiY := -1;
  26.  
  27.   // Remember current stream position because procedure is called also from
  28.   // jpeg Exif block.
  29.  
  30. ...
  31.  
  32.   case units of
  33.     1: begin dpiX := 96; dpiY := 96; end;
  34.     2: ;  // is already inches, nothing to do
  35.     3: begin dpiX := dpiX*2.54; dpiY := dpiY * 2.54; end;
  36.   end;
  37.  
  38.   if (dpiX = -1) then dpiX := initialDPIX; // Restore if reading failed
  39.   if (dpiY = -1) then dpiY := initialDPIY;
  40.  
  41.   Result := true;
  42.  

I hope these small corrections are useful! And first of all, thank you very much for this beautiful library!

Greetings from Brazil,

Roni.

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #1 on: May 07, 2025, 11:00:04 pm »
Thank you. Could you please add images for which the current procedure fails so that I can verify the issue?

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #2 on: May 07, 2025, 11:12:27 pm »
Thank you. Could you please add images for which the current procedure fails so that I can verify the issue?

Sure! Attached is one of the images present in the XLSX spreadsheet that causes this problem...

Thanks!

Roni.

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #3 on: May 08, 2025, 02:01:51 pm »
It seems to me that the first issue is that this is some kind of "malformed" jpeg image because it contains two JFIF segments ($FF $E0), not sure whether this is allowed or not, but I've never seen this before. But anyway, other software can handle this file, therefore, FPSpreadsheet should be able to, as well.

The other issue is that the function GetJpgSize of fpspreadsheet can exit before all segments have been scanned in which the required information can be found. And the TIFF metadata routine may destroy values already collected.

I uploaded an improved version to svn, at least it detects your test file correctly, and spready is able to display an xlsx file in which that jpg file is embedded. Since there is some rework of the chart part of fpspreadsheet at the moment the current version will not yet be made available in OPM. If you don't use the svn version you probably can just use the file fpsimage.pas in your fpspreadsheet folder (not 100% sure if this works, therefore make a backup copy of this file before you change anything).

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #4 on: May 09, 2025, 07:49:31 pm »
Hello wp!
I had never seen an image like this before, and the spreadsheet I received from a client has over 100 images with this problem (there is still one unrecognized image, but I haven't identified which one to debug yet).

Even so, thanks for putting it in svn, for now I changed it directly in fpsimages.pas. It doesn't fix it in development time, but it does fix it in runtime!   :)

Another thing I noticed is that the figures don't resize along with the changes in ZoomFactor. In which file is this handled? I can take a look to see if I can do something about it too.

Thanks for the quick response!

Cheers,

Roni

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #5 on: May 09, 2025, 09:16:52 pm »
It doesn't fix it in development time, but it does fix it in runtime!
Sorry, I don't understand what you mean

Another thing I noticed is that the figures don't resize along with the changes in ZoomFactor.
Which figures? The width and height of the image? IIRC, this should be considered before scaling. Similar to column width or row height. The zoom factor should be applied at the end and should not modify any dimensions stored in the worksheet/workbook.

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #6 on: May 09, 2025, 11:26:21 pm »
It doesn't fix it in development time, but it does fix it in runtime!
Sorry, I don't understand what you mean

When I connect to this spreadsheet inside Lazarus (design time) through TsWorkbookSource, I get several error messages (one for each image), but when I run the compiled application (runtime), I don't get any error messages when opening the spreadsheet. This is not a problem for me now, the most important thing is not to have errors during the execution of the application. Don't worry about that. ;)

Another thing I noticed is that the figures don't resize along with the changes in ZoomFactor.
Which figures? The width and height of the image? IIRC, this should be considered before scaling. Similar to column width or row height. The zoom factor should be applied at the end and should not modify any dimensions stored in the worksheet/workbook.

Yes, the height and width of the image... When a ZoomFactor is applied (say, 50%), in Excel the images also decrease by 50% (or close to that). In FPSpreadsheet the figures maintain the same size, even if the ZoomFactor is increased or decreased a lot (say 5%). It is only in the figure presentation routine, I don't want to change anything in the figure properties. Since I don't know the FPSpreadsheet file tree, I don't know in which file I should look for the routine that shows the images on the TsWorksheetGrid canvas...

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #7 on: May 10, 2025, 12:15:12 am »
Ah, I see, you did not mention TsWorksheetGrid. Yes, I think the scale factor was ignored when image support was added later.

The new svn version fixes this. If you do not use svn you can patch it yourself: All the changes are in the method TsCustomWorksheetGrid.DrawImages in unit source/visual/fpsworksheetgrid.pas. Essentially, multiply img^.ScaleX, img^.ScaleY, img^.OffsetX and img^.OffsetY by Workbook.ZoomFactor - that's it.

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #8 on: May 14, 2025, 10:14:19 pm »
Hello wp!

I apologize for the delay in responding, I was looking for another small bug... And sorry for not mentioning TsWorksheetGrid... It's not because it seemed obvious to me, that it really was obvious to everyone... Duh....  %)

I noticed that the IMG object was being written to memory with the RowOffset and ColOffset inverted... This happens in the XLSXOOXML unit, inside the TsSpreadOOXMLReader.ReadEmbeddedObjs procedure.

At the end of the procedure, the WriteImage function of the sheet object (TsWorkSheet) is called. See the call:

Code: Pascal  [Select][+][-]
  1. sheet.WriteImage(data.FromRow, data.FromCol,
  2. data.ImgIndex,
  3. data.FromRowOffs, data.FromColOffs,
  4. scaleX, scaleY
  5. );
  6.  

The function declaration (in the include fpspreadsheet_embobj.inc) is this:

Code: Pascal  [Select][+][-]
  1. function TsWorksheet.WriteImage(ARow, ACol: Cardinal; AImageIndex: Integer;
  2. AOffsetX: Double = 0.0; AOffsetY: Double = 0.0;
  3. AScaleX: Double = 1.0; AScaleY: Double = 1.0): Integer;
  4.  

Note that "data.FromRowOffs" is being passed to AOffsetX (X should be columns) and "data.data.FromColOffs" to AOffsetY (Y should be rows).

Just invert this call and the objects started to appear in the correct positions. I don't know if this change has any impact on other points of the code, but in my test spreadsheet everything started to be in the correct place.

Fixed function call:

Code: Pascal  [Select][+][-]
  1. sheet.WriteImage(data.FromRow, data.FromCol,
  2. data.ImgIndex,
  3. data.FromColOffs, data.FromRowOffs,
  4. scaleX, scaleY
  5. );
  6.  

Hope this helps! I'm still trying to figure out why several images aren't being displayed in the Grid. If I find out, I'll let you know here too!

Thanks for this wonderful library!

Roni

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #9 on: May 15, 2025, 02:32:44 am »
Update:

Some images are not being loaded because they are in oneCellAnchor nodes. I tried to modify the TsSpreadOOXMLReader.ReadDrawing procedure by setting a condition to handle oneCellAnchor but node.nodeName does not have the sibling of "xdr:pic", only "xdr:from", "xdr:ext" and "xdr:clientData". In "drawing1.xml" there is the sibling "xdr:pic". I still haven't found the reason because it is not in the node chain. Below is part of one of the figures in drawing1.xml:

Code: XML  [Select][+][-]
  1. <xdr:oneCellAnchor>
  2.         <xdr:from>
  3.                 <xdr:col>1</xdr:col>
  4.                 <xdr:colOff>304800</xdr:colOff>
  5.                 <xdr:row>4</xdr:row>
  6.                 <xdr:rowOff>54430</xdr:rowOff>
  7.         </xdr:from>
  8.         <xdr:ext cx="2409319"
  9.                  cy="3111500"/>
  10.         <xdr:pic>
  11.                 <xdr:nvPicPr>
  12.                         <xdr:cNvPr id="208"
  13.                                    name="Imagem 207">
  14.                                 <a:extLst>
  15.                                         <a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}">
  16.                                                 <a16:creationId xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main"
  17.                                                                 id="{26A5D329-270E-4377-A62E-97E02076F5F6}"/>
  18.                                         </a:ext>
  19.                                 </a:extLst>
  20.                         </xdr:cNvPr>
  21.                         <xdr:cNvPicPr>
  22.                                 <a:picLocks noChangeAspect="1"
  23.                                             noChangeArrowheads="1"/>
  24.                         </xdr:cNvPicPr>
  25.                 </xdr:nvPicPr>
  26.                 <xdr:blipFill>
  27.                         <a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  28.                                 r:embed="rId4"
  29.                                 cstate="email">
  30.                                 <a:extLst>
  31.                                         <a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
  32.                                                 <a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"/>
  33.                                         </a:ext>
  34.                                 </a:extLst>
  35.                         </a:blip>
  36.                         <a:srcRect/>
  37.                         <a:stretch>
  38.                                 <a:fillRect/>
  39.                         </a:stretch>
  40.                 </xdr:blipFill>
  41.                 <xdr:spPr bwMode="auto">
  42.                         <a:xfrm>
  43.                                 <a:off x="80505300"
  44.                                        y="33391930"/>
  45.                                 <a:ext cx="2409319"
  46.                                        cy="3111500"/>
  47.                         </a:xfrm>
  48.                         <a:prstGeom prst="rect">
  49.                                 <a:avLst/>
  50.                         </a:prstGeom>
  51.                         <a:noFill/>
  52.                         <a:extLst>
  53.                                 <a:ext uri="{909E8E84-426E-40DD-AFC4-6F175D3DCCD1}">
  54.                                         <a14:hiddenFill xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main">
  55.                                                 <a:solidFill>
  56.                                                         <a:srgbClr val="FFFFFF"/>
  57.                                                 </a:solidFill>
  58.                                         </a14:hiddenFill>
  59.                                 </a:ext>
  60.                         </a:extLst>
  61.                 </xdr:spPr>
  62.         </xdr:pic>
  63.         <xdr:clientData/>
  64. </xdr:oneCellAnchor>
  65.  

I will continue debugging, and if I find anything, I will post it here.

Thanks!

Roni

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #10 on: May 15, 2025, 11:21:50 pm »
Just invert this call [...]
Thanks for this fix. But it's only half of the story. The same exchanged x and y coordinates were found also in TsWorksheet.CalcImageExtent which was responsible for the bug in writing:

Code: Pascal  [Select][+][-]
  1. procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
  2.   out ARow1, ACol1, ARow2, ACol2: Cardinal;
  3.   out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;
  4.   out x,y, AWidth, AHeight: Double);
  5. var
  6.   img: TsImage;
  7.   obj: TsEmbeddedObj;
  8. begin
  9.   img := GetImage(AIndex);
  10.   ARow1 := img.Row;
  11.   ACol1 := img.Col;
  12.   ARowOffs1 := img.OffsetX;            // in workbook units  --- was: AColOffs1 := img.OffsetX
  13.   AColOffs1 := img.OffsetY;            // in workbook units  --- was: ARowOffs1 := img.OffsetY;
  14.  
  15.   obj := FWorkbook.GetEmbeddedObj(img.Index);
  16. [...]

Some images are not being loaded because they are in oneCellAnchor nodes.
Unfortunately embedded images are handled by Excel in a very confusing way. Thank you for debugging this. I usually approach this issue by writing small Excel files where only a single property is changed in a well-known way. Then I study the content of the xml files embedded inside the xlsx file structure. To simplify the many steps of unzipping, opening the xmls, navigating to the interesting nodes etc I wrote a simple utility "ZippedFileViewer" (https://github.com/wp-xyz/ZippedFileViewer)
« Last Edit: May 15, 2025, 11:29:35 pm by wp »

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #11 on: May 16, 2025, 11:35:51 pm »
Just invert this call [...]
Thanks for this fix. But it's only half of the story. The same exchanged x and y coordinates were found also in TsWorksheet.CalcImageExtent which was responsible for the bug in writing:

Code: Pascal  [Select][+][-]
  1. procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
  2.   out ARow1, ACol1, ARow2, ACol2: Cardinal;
  3.   out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;
  4.   out x,y, AWidth, AHeight: Double);
  5. var
  6.   img: TsImage;
  7.   obj: TsEmbeddedObj;
  8. begin
  9.   img := GetImage(AIndex);
  10.   ARow1 := img.Row;
  11.   ACol1 := img.Col;
  12.   ARowOffs1 := img.OffsetX;            // in workbook units  --- was: AColOffs1 := img.OffsetX
  13.   AColOffs1 := img.OffsetY;            // in workbook units  --- was: ARowOffs1 := img.OffsetY;
  14.  
  15.   obj := FWorkbook.GetEmbeddedObj(img.Index);
  16. [...]

Some images are not being loaded because they are in oneCellAnchor nodes.
Unfortunately embedded images are handled by Excel in a very confusing way. Thank you for debugging this. I usually approach this issue by writing small Excel files where only a single property is changed in a well-known way. Then I study the content of the xml files embedded inside the xlsx file structure. To simplify the many steps of unzipping, opening the xmls, navigating to the interesting nodes etc I wrote a simple utility "ZippedFileViewer" (https://github.com/wp-xyz/ZippedFileViewer)

Hello wp!
It really is quite confusing and the documentation is very sparse. I only started to understand something after reading a lot about DrawingML. That is, if I really managed to understand anything...

I'm going to change the CalcImageExtent procedure, because I'll need to save the spreadsheet as well. Thank you very much!

I've already downloaded your "ZippedFileViewer" project, but I still need to compile it. It will definitely be very useful. Thank you!

I managed to add the processing of the "oneCellAnchor" nodes, I did some tests and everything seems to be working correctly. In my case, I still need to make some changes, because some figures in the XLSX spreadsheet are rotated. I'm already capturing the rotation in the routine, but the TEmbeddedObjData object doesn't have this property. Furthermore, in the FPSpreadsheetGrid unit it would be necessary to change the "TsCustomWorksheetGrid.DrawImages" procedure to handle this rotation. I won't have time to implement this now, but at least I'm already capturing the rotation angle of the image.

Below is the procedure "TsSpreadOOXMLReader.ReadDrawing" with my modifications. If you find them useful, feel free to incorporate them into the library.

Question: How can I edit a post in this forum? I couldn't find where to do that...

Roni

Code: Pascal  [Select][+][-]
  1. procedure TsSpreadOOXMLReader.ReadDrawing(ANode: TDOMNode;
  2.   AWorksheet: TsBasicWorksheet);
  3. var
  4.   node, child, child2, child3: TDOMNode;
  5.   nodeName: String = '';
  6.   rID, fileName, stmp: String;
  7.   fromCol, fromRow, toCol, toRow: Integer;
  8.   fromColOffs, fromRowOffs, toColOffs, toRowOffs: Double;
  9.   data: TEmbeddedObjData;
  10.   sheetData: TSheetData;
  11.   RotateAngle: Double;
  12. begin
  13.   if ANode = nil then
  14.     exit;
  15.  
  16.   sheetData := TSheetData(FSheetList[TsWorksheet(AWorksheet).Index]);
  17.   ANode := ANode.FirstChild;
  18.  
  19.   while Assigned(ANode) do
  20.   begin
  21.     nodeName := ANode.NodeName;
  22.     fromCol := -1;     fromColOffs := 0.0;
  23.     fromRow := -1;     fromRowOffs := 0.0;
  24.     toCol := -1;       toColOffs := 0.0;
  25.     toRow := -1;       toRowOffs := 0.0;
  26.     rID := '';         fileName := '';
  27.     if nodeName = 'xdr:twoCellAnchor' then
  28.     begin
  29.       node := ANode.FirstChild;
  30.       while Assigned(node) do begin
  31.         nodeName := node.NodeName;
  32.         if nodeName = 'xdr:from' then
  33.         begin
  34.           child := node.FirstChild;
  35.           while Assigned(child) do begin
  36.             nodeName := child.NodeName;
  37.             if nodeName = 'xdr:col' then
  38.               fromCol := StrToIntDef(GetNodeValue(child), -1)
  39.             else if nodeName = 'xdr:row' then
  40.               fromRow := StrToIntDef(GetNodeValue(child), -1)
  41.             else if nodeName = 'xdr:colOff' then
  42.               fromColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))
  43.             else if nodeName = 'xdr:rowOff' then
  44.               fromRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));
  45.             child := child.NextSibling;
  46.           end;
  47.         end else
  48.         if nodeName = 'xdr:to' then
  49.         begin
  50.           child := node.FirstChild;
  51.           while Assigned(child) do begin
  52.             nodeName := child.NodeName;
  53.             if nodeName = 'xdr:col' then
  54.               toCol := StrToIntDef(GetNodeValue(child), -1)
  55.             else if nodeName = 'xdr:row' then
  56.               toRow := StrToIntDef(GetNodeValue(child), -1)
  57.             else if nodeName = 'xdr:colOff' then
  58.               toColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))
  59.             else if nodeName = 'xdr:rowOff' then
  60.               toRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));
  61.             child := child.NextSibling;
  62.           end;
  63.         end else
  64.         if nodeName = 'xdr:pic' then
  65.         begin
  66.           child := node.FirstChild;
  67.           while Assigned(child) do begin
  68.             nodeName := child.NodeName;
  69.             if nodeName = 'xdr:blipFill' then
  70.             begin
  71.               child2 := child.FirstChild;
  72.               while Assigned(child2) do begin
  73.                 nodeName := child2.NodeName;
  74.                 if nodeName = 'a:blip' then
  75.                   rID := GetAttrValue(child2, 'r:embed');
  76.                 child2 := child2.NextSibling;
  77.               end;
  78.             end else
  79.             if nodeName = 'xdr:nvPicPr' then begin
  80.               child2 := child.FirstChild;
  81.               while Assigned(child2) do begin
  82.                 nodeName := child2.NodeName;
  83.                 if nodeName = 'xdr:cNvPr' then
  84.                   fileName := GetAttrValue(child2, 'descr');
  85.                 child2 := child2.NextSibling;
  86.               end;
  87.             end;
  88.             child := child.NextSibling;
  89.           end;
  90.         end;
  91.         node := node.NextSibling;
  92.       end;
  93.     end;
  94.  
  95.     // oneCellAnchor
  96.     if nodeName = 'xdr:oneCellAnchor' then
  97.     begin
  98.       node := ANode.FirstChild;
  99.       while Assigned(node) do begin
  100.         nodeName := node.NodeName;
  101.         if nodeName = 'xdr:from' then
  102.         begin
  103.           child := node.FirstChild;
  104.           while Assigned(child) do begin
  105.             nodeName := child.NodeName;
  106.             if nodeName = 'xdr:col' then
  107.               fromCol := StrToIntDef(GetNodeValue(child), -1)
  108.             else if nodeName = 'xdr:row' then
  109.               fromRow := StrToIntDef(GetNodeValue(child), -1)
  110.             else if nodeName = 'xdr:colOff' then
  111.               fromColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))
  112.             else if nodeName = 'xdr:rowOff' then
  113.               fromRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));
  114.             child := child.NextSibling;
  115.           end;
  116.         end else
  117.         if nodeName = 'xdr:pic' then
  118.         begin
  119.           child := node.FirstChild;
  120.           while Assigned(child) do begin
  121.             nodeName := child.NodeName;
  122.             if nodeName = 'xdr:blipFill' then
  123.             begin
  124.               child2 := child.FirstChild;
  125.               while Assigned(child2) do begin
  126.                 nodeName := child2.NodeName;
  127.                 if nodeName = 'a:blip' then
  128.                   rID := GetAttrValue(child2, 'r:embed');
  129.                 child2 := child2.NextSibling;
  130.               end;
  131.             end else
  132.             if nodeName = 'xdr:nvPicPr' then begin
  133.               child2 := child.FirstChild;
  134.               while Assigned(child2) do begin
  135.                 nodeName := child2.NodeName;
  136.                 if nodeName = 'xdr:cNvPr' then
  137.                   fileName := GetAttrValue(child2, 'descr');
  138.                 child2 := child2.NextSibling;
  139.               end;
  140.             end;
  141.               if nodeName = 'xdr:spPr' then begin
  142.               child2 := child.FirstChild;
  143.               while Assigned(child2) do begin
  144.                 nodeName := child2.NodeName;
  145.                 if nodeName = 'a:xfrm' then begin
  146.                   RotateAngle := StrToFloatDef(GetAttrValue(child2, 'rot'),0) / 60000;   // Rotation of the image. 60000 is 1 degree
  147.                   child3 := child2.FirstChild;
  148.                   while Assigned(child3) do begin
  149.                     nodeName:=child3.NodeName;
  150.                     if nodeName = 'a:ext' then begin
  151.                       toColOffs := EMUToMM(StrToInt64Def(GetAttrValue(child3, 'cx'),0)) + fromColOffs;  // cx is the width of the image in EMU's
  152.                       toRowOffs := EMUToMM(StrToInt64Def(GetAttrValue(child3, 'cy'),0)) + fromRowOffs;  // cy is the height of the image in EMU's
  153.                     end;
  154.                     child3 := child3.NextSibling;
  155.                   end;
  156.                 end;
  157.                 child2 := child2.NextSibling;
  158.               end;
  159.             end;
  160.             child := child.NextSibling;
  161.           end;
  162.         end;
  163.         node := node.NextSibling;
  164.       end;
  165.       toCol:=fromCol;
  166.       toRow:=fromRow;
  167.     end;
  168.  
  169.     if (fromCol <> -1) and (toCol <> -1) and (fromRow <> -1) and (toRow <> -1) and (rID <> '') then
  170.     begin
  171.       data := TEmbeddedObjData.Create;
  172.       data.FromCol := fromCol;
  173.       data.FromColOffs := fromColOffs;
  174.       data.ToCol := toCol;
  175.       data.ToColOffs := toColOffs;
  176.       data.FromRow := fromRow;
  177.       data.FromRowOffs := fromRowOffs;
  178.       data.ToRow := toRow;
  179.       data.ToRowOffs := toRowOffs;
  180.       data.RelId := rId;
  181.       data.FileName := fileName;
  182.       data.MediaName := MakeXLPath(sheetData.DrawingRels.FindTarget(rID));
  183.       data.ImgIndex := -1;
  184.       data.Worksheet := AWorksheet;
  185.       data.IsHeaderFooter := false;
  186.       FEmbeddedObjList.Add(data);
  187.     end;
  188.      
  189.     ANode := ANode.NextSibling;
  190.   end;
  191. end;
  192.  

wp

  • Hero Member
  • *****
  • Posts: 12800
Re: A little bug in fpsimages.pas
« Reply #12 on: May 17, 2025, 12:31:24 am »
Can you send me an xlsx file with an embedded image in one-cell-anchor mode (ideally only a single image in the file). Whatever I do in the GUI - I always end up with two-cell-anchor nodes in the xml.

Question: How can I edit a post in this forum? I couldn't find where to do that...
At the top of your posts, right-hand side, there is a "Modify" button. Of course, it works only for your own posts.
« Last Edit: May 17, 2025, 12:58:48 am by wp »

Roni Wolf

  • New member
  • *
  • Posts: 8
Re: A little bug in fpsimages.pas
« Reply #13 on: May 18, 2025, 09:38:07 pm »
Hello wp!

Can you send me an xlsx file with an embedded image in one-cell-anchor mode (ideally only a single image in the file). Whatever I do in the GUI - I always end up with two-cell-anchor nodes in the xml.

Yes, sure!

In the "oneCellAnchor test 1.XLSX" spreadsheet, I left 2 images, one in oneCellAnchor and the other in twoCellAnchor. I believe that the difference is in the size of the image in relation to the size of the cell (height and width). When the original size of the image is smaller than the size of the cell, Excel should save it in the oneCellAnchor node, and when the image is larger, Excel saves it in the twoCellAnchor node. At least that's what I deduced.

I believe that other changes will be necessary, because if the library was not prepared to read images within oneCellAnchor node, it probably will not be saving images within a oneCellAnchor either. Another task for my "ToDo" list.  ;)

If you need anything else, just ask me.

Question: How can I edit a post in this forum? I couldn't find where to do that...
At the top of your posts, right-hand side, there is a "Modify" button. Of course, it works only for your own posts.

I don't know why, but this option doesn't appear for me, I believe it's because I'm new to the forum.

Thanks for your help.

Roni

 

TinyPortal © 2005-2018