[{"data":1,"prerenderedAt":2571},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel":2537,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel":2546,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel":2551,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel":2552},{"id":4,"title":5,"body":6,"dateModified":2504,"datePublished":2504,"description":2505,"extension":2506,"faq":2507,"keyword":2516,"kind":2517,"meta":2518,"navigation":116,"path":2526,"seo":2527,"slug":2530,"stem":2531,"topics":2532,"type":2535,"__hash__":2536},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel\u002Findex.md","Build a Product Catalog with Images in Excel",{"type":7,"value":8,"toc":2493},"minimark",[9,24,29,64,74,289,472,476,479,739,753,757,760,958,969,1082,1086,1097,1770,1776,1951,1954,1958,1968,2078,2119,2372,2376,2414,2418,2428,2432,2438,2444,2450,2456,2460,2489],[10,11,12,13,17,18,23],"p",{},"Sales teams, buyers and shop staff often want the product list ",[14,15,16],"em",{},"with pictures",": a price list for a trade fair, a range review, an order form for a franchise shop. The data is usually in a spreadsheet and the photos in a folder, named after the SKU. Putting them together by hand is slow, and the obvious script — insert every photo at full size — produces a 200 MB workbook with pictures that spill across rows and stay put when someone sorts by price. Doing it well takes four decisions: shrink the photos first, fit each into a fixed cell box without distorting it, handle products with no photo, and anchor the pictures so they travel with their rows. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002F","Inserting Images and Logos into Excel",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas pillow xlsxwriter openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55,58,61],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pandas",[40,56,57],{"class":50}," pillow",[40,59,60],{"class":50}," xlsxwriter",[40,62,63],{"class":50}," openpyxl\n",[10,65,66,69,70,73],{},[37,67,68],{},"products.xlsx"," lists seven products with SKU, name, category and price. The ",[37,71,72],{},"photos\u002F"," folder holds a JPEG per SKU at camera-like sizes — 2400 × 1600 or 1600 × 2000 pixels, some landscape and some portrait — and one product, the desk lamp, has no photo yet.",[30,75,79],{"className":76,"code":77,"language":78,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from pathlib import Path\nimport pandas as pd\n\nproducts = pd.read_excel(\"products.xlsx\")\nphotos = {p.stem: p for p in Path(\"photos\").glob(\"*.jpg\")}\nprint(len(products), \"products,\", len(photos), \"photos\")\nprint(\"without photo:\", sorted(set(products[\"SKU\"]) - set(photos)))\nprint(f\"photo folder: {sum(p.stat().st_size for p in photos.values()) \u002F 1e6:.1f} MB\")\n","python",[37,80,81,97,111,118,136,171,202,240],{"__ignoreMap":35},[40,82,83,87,91,94],{"class":42,"line":43},[40,84,86],{"class":85},"s-kum","from",[40,88,90],{"class":89},"skGVy"," pathlib ",[40,92,93],{"class":85},"import",[40,95,96],{"class":89}," Path\n",[40,98,100,102,105,108],{"class":42,"line":99},2,[40,101,93],{"class":85},[40,103,104],{"class":89}," pandas ",[40,106,107],{"class":85},"as",[40,109,110],{"class":89}," pd\n",[40,112,114],{"class":42,"line":113},3,[40,115,117],{"emptyLinePlaceholder":116},true,"\n",[40,119,121,124,127,130,133],{"class":42,"line":120},4,[40,122,123],{"class":89},"products ",[40,125,126],{"class":85},"=",[40,128,129],{"class":89}," pd.read_excel(",[40,131,132],{"class":50},"\"products.xlsx\"",[40,134,135],{"class":89},")\n",[40,137,139,142,144,147,150,153,156,159,162,165,168],{"class":42,"line":138},5,[40,140,141],{"class":89},"photos ",[40,143,126],{"class":85},[40,145,146],{"class":89}," {p.stem: p ",[40,148,149],{"class":85},"for",[40,151,152],{"class":89}," p ",[40,154,155],{"class":85},"in",[40,157,158],{"class":89}," Path(",[40,160,161],{"class":50},"\"photos\"",[40,163,164],{"class":89},").glob(",[40,166,167],{"class":50},"\"*.jpg\"",[40,169,170],{"class":89},")}\n",[40,172,174,178,181,184,187,190,193,195,198,200],{"class":42,"line":173},6,[40,175,177],{"class":176},"sP0c6","print",[40,179,180],{"class":89},"(",[40,182,183],{"class":176},"len",[40,185,186],{"class":89},"(products), ",[40,188,189],{"class":50},"\"products,\"",[40,191,192],{"class":89},", ",[40,194,183],{"class":176},[40,196,197],{"class":89},"(photos), ",[40,199,161],{"class":50},[40,201,135],{"class":89},[40,203,205,207,209,212,214,217,219,222,225,228,231,234,237],{"class":42,"line":204},7,[40,206,177],{"class":176},[40,208,180],{"class":89},[40,210,211],{"class":50},"\"without photo:\"",[40,213,192],{"class":89},[40,215,216],{"class":176},"sorted",[40,218,180],{"class":89},[40,220,221],{"class":176},"set",[40,223,224],{"class":89},"(products[",[40,226,227],{"class":50},"\"SKU\"",[40,229,230],{"class":89},"]) ",[40,232,233],{"class":85},"-",[40,235,236],{"class":176}," set",[40,238,239],{"class":89},"(photos)))\n",[40,241,243,245,247,250,253,257,260,263,265,267,269,272,275,278,281,284,287],{"class":42,"line":242},8,[40,244,177],{"class":176},[40,246,180],{"class":89},[40,248,249],{"class":85},"f",[40,251,252],{"class":50},"\"photo folder: ",[40,254,256],{"class":255},"sSjpA","{",[40,258,259],{"class":176},"sum",[40,261,262],{"class":89},"(p.stat().st_size ",[40,264,149],{"class":85},[40,266,152],{"class":89},[40,268,155],{"class":85},[40,270,271],{"class":89}," photos.values()) ",[40,273,274],{"class":85},"\u002F",[40,276,277],{"class":176}," 1e6",[40,279,280],{"class":85},":.1f",[40,282,283],{"class":255},"}",[40,285,286],{"class":50}," MB\"",[40,288,135],{"class":89},[290,291,299,300,299,304,299,308,299,316,299,326,299,337,299,344,299,348,299,353,299,359,299,364,299,368,299,375,299,381,299,385,299,389,299,392,299,395,299,401,299,405,299,409,299,412,299,416,299,419,299,424,299,431,299,436,299,439,299,442,299,446,299,449,299,454,299,461,299,466,299,469],"svg",{"viewBox":292,"role":293,"ariaLabelledBy":294,"xmlns":297,"style":298},"4 0 752 142","img",[295,296],"cat-flow-t","cat-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[301,302,303],"title",{"id":295},"From a list and a photo folder to a catalogue",[305,306,307],"desc",{"id":296},"The product list is matched to photos by SKU, each photo is shrunk to a thumbnail, fitted into a fixed cell box without distortion, and inserted so it moves and sizes with its cell; products without a photo get a placeholder text.",[309,310],"rect",{"x":311,"y":312,"width":313,"height":314,"fill":315},"4","0","752","142","#ffffff",[309,317],{"x":318,"y":319,"width":320,"height":321,"rx":322,"fill":323,"stroke":324,"style":325},"20.0","16.0","115.2","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[327,328,330],"g",{"transform":329},"translate(65.6,29.0) scale(1.000)",[331,332],"path",{"d":333,"fill":334,"stroke":335,"style":336},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[338,339,343],"text",{"x":340,"y":341,"style":342},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Match by",[338,345,347],{"x":340,"y":346,"style":342},"93.0","SKU",[338,349,352],{"x":340,"y":350,"style":351},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","list ↔ photos\u002F",[42,354],{"x1":355,"y1":356,"x2":357,"y2":356,"stroke":358,"style":325},"140.2","71.2","162.2","var(--brand,#5b5cf0)",[360,361],"polygon",{"points":362,"fill":363},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[309,365],{"x":366,"y":319,"width":320,"height":321,"rx":322,"fill":367,"stroke":358,"style":325},"171.2","#f0f4ff",[327,369,371],{"transform":370},"translate(216.8,37.8) scale(1.000)",[331,372],{"d":373,"fill":334,"stroke":374,"style":336},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01","var(--brand-strong,#4338ca)",[338,376,380],{"x":377,"y":378,"style":379},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Shrink",[338,382,384],{"x":377,"y":383,"style":351},"103.8","Pillow thumbnail",[42,386],{"x1":387,"y1":356,"x2":388,"y2":356,"stroke":358,"style":325},"291.4","313.4",[360,390],{"points":391,"fill":363},"317.4,71.2 308.4,76.2 308.4,66.2",[309,393],{"x":394,"y":319,"width":320,"height":321,"rx":322,"fill":367,"stroke":358,"style":325},"322.4",[327,396,398],{"transform":397},"translate(368.0,29.0) scale(1.000)",[331,399],{"d":400,"fill":334,"stroke":374,"style":336},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[338,402,404],{"x":403,"y":341,"style":379},"380.0","Fit the box",[338,406,408],{"x":403,"y":407,"style":351},"95.0","keep aspect",[338,410,411],{"x":403,"y":350,"style":351},"ratio",[42,413],{"x1":414,"y1":356,"x2":415,"y2":356,"stroke":358,"style":325},"442.6","464.6",[360,417],{"points":418,"fill":363},"468.6,71.2 459.6,76.2 459.6,66.2",[309,420],{"x":421,"y":319,"width":320,"height":321,"rx":322,"fill":422,"stroke":423,"style":325},"473.6","#d9f4f1","var(--teal,#0f9488)",[327,425,427],{"transform":426},"translate(519.2,29.0) scale(1.000)",[331,428],{"d":429,"fill":334,"stroke":430,"style":336},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[338,432,435],{"x":433,"y":341,"style":434},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Insert",[338,437,438],{"x":433,"y":407,"style":351},"move and size",[338,440,441],{"x":433,"y":350,"style":351},"with cells",[42,443],{"x1":444,"y1":356,"x2":445,"y2":356,"stroke":358,"style":325},"593.8","615.8",[360,447],{"points":448,"fill":363},"619.8,71.2 610.8,76.2 610.8,66.2",[309,450],{"x":451,"y":319,"width":320,"height":321,"rx":322,"fill":452,"stroke":453,"style":325},"624.8","#fdefd8","var(--gold,#b4740a)",[327,455,457],{"transform":456},"translate(670.4,29.0) scale(1.000)",[331,458],{"d":459,"fill":334,"stroke":460,"style":336},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[338,462,465],{"x":463,"y":341,"style":464},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Missing",[338,467,468],{"x":463,"y":346,"style":464},"photo",[338,470,471],{"x":463,"y":350,"style":351},"placeholder text",[25,473,475],{"id":474},"step-1-shrink-every-photo-before-inserting-it","Step 1 — Shrink every photo before inserting it",[10,477,478],{},"Excel stores each picture at its original resolution, however small it is displayed. A 2400-pixel photo shown as a 120-pixel thumbnail still weighs its full megabytes in the file. Resize with Pillow first, to about twice the display size so thumbnails stay sharp on high-density screens:",[30,480,482],{"className":76,"code":481,"language":78,"meta":35,"style":35},"import io\nfrom PIL import Image\n\nBOX_W, BOX_H = 120, 90            # thumbnail box in pixels, as displayed\n\ndef thumbnail(path, scale=2):\n    with Image.open(path) as img:\n        img.thumbnail((BOX_W * scale, BOX_H * scale))\n        buf = io.BytesIO()\n        img.convert(\"RGB\").save(buf, format=\"JPEG\", quality=85, optimize=True)\n        return buf, img.size\n\nbuf, size = thumbnail(photos[\"TB-200\"])\nwith Image.open(photos[\"TB-200\"]) as original:\n    print(\"original\", original.size, \"-> thumbnail\", size, f\"{len(buf.getvalue()) \u002F 1024:.1f} KB\")\n",[37,483,484,491,504,508,533,537,557,570,590,601,644,653,658,675,693],{"__ignoreMap":35},[40,485,486,488],{"class":42,"line":43},[40,487,93],{"class":85},[40,489,490],{"class":89}," io\n",[40,492,493,495,498,501],{"class":42,"line":99},[40,494,86],{"class":85},[40,496,497],{"class":176}," PIL",[40,499,500],{"class":85}," import",[40,502,503],{"class":89}," Image\n",[40,505,506],{"class":42,"line":113},[40,507,117],{"emptyLinePlaceholder":116},[40,509,510,513,515,518,521,524,526,529],{"class":42,"line":120},[40,511,512],{"class":176},"BOX_W",[40,514,192],{"class":89},[40,516,517],{"class":176},"BOX_H",[40,519,520],{"class":85}," =",[40,522,523],{"class":176}," 120",[40,525,192],{"class":89},[40,527,528],{"class":176},"90",[40,530,532],{"class":531},"s-wDw","            # thumbnail box in pixels, as displayed\n",[40,534,535],{"class":42,"line":138},[40,536,117],{"emptyLinePlaceholder":116},[40,538,539,542,546,549,551,554],{"class":42,"line":173},[40,540,541],{"class":85},"def",[40,543,545],{"class":544},"s_Opv"," thumbnail",[40,547,548],{"class":89},"(path, scale",[40,550,126],{"class":85},[40,552,553],{"class":176},"2",[40,555,556],{"class":89},"):\n",[40,558,559,562,565,567],{"class":42,"line":204},[40,560,561],{"class":85},"    with",[40,563,564],{"class":89}," Image.open(path) ",[40,566,107],{"class":85},[40,568,569],{"class":89}," img:\n",[40,571,572,575,577,580,583,585,587],{"class":42,"line":242},[40,573,574],{"class":89},"        img.thumbnail((",[40,576,512],{"class":176},[40,578,579],{"class":85}," *",[40,581,582],{"class":89}," scale, ",[40,584,517],{"class":176},[40,586,579],{"class":85},[40,588,589],{"class":89}," scale))\n",[40,591,593,596,598],{"class":42,"line":592},9,[40,594,595],{"class":89},"        buf ",[40,597,126],{"class":85},[40,599,600],{"class":89}," io.BytesIO()\n",[40,602,604,607,610,613,617,619,622,624,627,629,632,634,637,639,642],{"class":42,"line":603},10,[40,605,606],{"class":89},"        img.convert(",[40,608,609],{"class":50},"\"RGB\"",[40,611,612],{"class":89},").save(buf, ",[40,614,616],{"class":615},"sa561","format",[40,618,126],{"class":85},[40,620,621],{"class":50},"\"JPEG\"",[40,623,192],{"class":89},[40,625,626],{"class":615},"quality",[40,628,126],{"class":85},[40,630,631],{"class":176},"85",[40,633,192],{"class":89},[40,635,636],{"class":615},"optimize",[40,638,126],{"class":85},[40,640,641],{"class":176},"True",[40,643,135],{"class":89},[40,645,647,650],{"class":42,"line":646},11,[40,648,649],{"class":85},"        return",[40,651,652],{"class":89}," buf, img.size\n",[40,654,656],{"class":42,"line":655},12,[40,657,117],{"emptyLinePlaceholder":116},[40,659,661,664,666,669,672],{"class":42,"line":660},13,[40,662,663],{"class":89},"buf, size ",[40,665,126],{"class":85},[40,667,668],{"class":89}," thumbnail(photos[",[40,670,671],{"class":50},"\"TB-200\"",[40,673,674],{"class":89},"])\n",[40,676,678,681,684,686,688,690],{"class":42,"line":677},14,[40,679,680],{"class":85},"with",[40,682,683],{"class":89}," Image.open(photos[",[40,685,671],{"class":50},[40,687,230],{"class":89},[40,689,107],{"class":85},[40,691,692],{"class":89}," original:\n",[40,694,696,699,701,704,707,710,713,715,718,720,722,725,727,730,732,734,737],{"class":42,"line":695},15,[40,697,698],{"class":176},"    print",[40,700,180],{"class":89},[40,702,703],{"class":50},"\"original\"",[40,705,706],{"class":89},", original.size, ",[40,708,709],{"class":50},"\"-> thumbnail\"",[40,711,712],{"class":89},", size, ",[40,714,249],{"class":85},[40,716,717],{"class":50},"\"",[40,719,256],{"class":255},[40,721,183],{"class":176},[40,723,724],{"class":89},"(buf.getvalue()) ",[40,726,274],{"class":85},[40,728,729],{"class":176}," 1024",[40,731,280],{"class":85},[40,733,283],{"class":255},[40,735,736],{"class":50}," KB\"",[40,738,135],{"class":89},[10,740,741,744,745,748,749,752],{},[37,742,743],{},"Image.thumbnail"," resizes in place to fit ",[14,746,747],{},"inside"," the given box and keeps the aspect ratio, so a landscape photo comes out wider than tall and a portrait photo taller than wide. Saving as JPEG at quality 85 with ",[37,750,751],{},"optimize=True"," gives small files with no visible loss at thumbnail size; use PNG only for logos and line drawings with sharp edges.",[25,754,756],{"id":755},"step-2-fit-each-thumbnail-into-a-fixed-cell-box","Step 2 — Fit each thumbnail into a fixed cell box",[10,758,759],{},"Every product row gets the same box: a fixed row height and column width. Scale the thumbnail to fit the box and offset it so it sits centred:",[30,761,763],{"className":76,"code":762,"language":78,"meta":35,"style":35},"def placement(size):\n    w, h = size\n    scale = min(BOX_W \u002F w, BOX_H \u002F h)\n    shown_w, shown_h = w * scale, h * scale\n    return {\"x_scale\": scale, \"y_scale\": scale,\n            \"x_offset\": int((BOX_W - shown_w) \u002F 2) + 4, \"y_offset\": int((BOX_H - shown_h) \u002F 2) + 4}\n\nfor sku in (\"TB-200\", \"CH-110\"):\n    print(sku, placement(thumbnail(photos[sku])[1]))\n",[37,764,765,775,785,812,833,853,920,924,945],{"__ignoreMap":35},[40,766,767,769,772],{"class":42,"line":43},[40,768,541],{"class":85},[40,770,771],{"class":544}," placement",[40,773,774],{"class":89},"(size):\n",[40,776,777,780,782],{"class":42,"line":99},[40,778,779],{"class":89},"    w, h ",[40,781,126],{"class":85},[40,783,784],{"class":89}," size\n",[40,786,787,790,792,795,797,799,802,805,807,809],{"class":42,"line":113},[40,788,789],{"class":89},"    scale ",[40,791,126],{"class":85},[40,793,794],{"class":176}," min",[40,796,180],{"class":89},[40,798,512],{"class":176},[40,800,801],{"class":85}," \u002F",[40,803,804],{"class":89}," w, ",[40,806,517],{"class":176},[40,808,801],{"class":85},[40,810,811],{"class":89}," h)\n",[40,813,814,817,819,822,825,828,830],{"class":42,"line":120},[40,815,816],{"class":89},"    shown_w, shown_h ",[40,818,126],{"class":85},[40,820,821],{"class":89}," w ",[40,823,824],{"class":85},"*",[40,826,827],{"class":89}," scale, h ",[40,829,824],{"class":85},[40,831,832],{"class":89}," scale\n",[40,834,835,838,841,844,847,850],{"class":42,"line":138},[40,836,837],{"class":85},"    return",[40,839,840],{"class":89}," {",[40,842,843],{"class":50},"\"x_scale\"",[40,845,846],{"class":89},": scale, ",[40,848,849],{"class":50},"\"y_scale\"",[40,851,852],{"class":89},": scale,\n",[40,854,855,858,861,864,867,869,872,875,877,880,883,886,889,891,894,896,898,900,902,904,907,909,911,913,915,917],{"class":42,"line":173},[40,856,857],{"class":50},"            \"x_offset\"",[40,859,860],{"class":89},": ",[40,862,863],{"class":176},"int",[40,865,866],{"class":89},"((",[40,868,512],{"class":176},[40,870,871],{"class":85}," -",[40,873,874],{"class":89}," shown_w) ",[40,876,274],{"class":85},[40,878,879],{"class":176}," 2",[40,881,882],{"class":89},") ",[40,884,885],{"class":85},"+",[40,887,888],{"class":176}," 4",[40,890,192],{"class":89},[40,892,893],{"class":50},"\"y_offset\"",[40,895,860],{"class":89},[40,897,863],{"class":176},[40,899,866],{"class":89},[40,901,517],{"class":176},[40,903,871],{"class":85},[40,905,906],{"class":89}," shown_h) ",[40,908,274],{"class":85},[40,910,879],{"class":176},[40,912,882],{"class":89},[40,914,885],{"class":85},[40,916,888],{"class":176},[40,918,919],{"class":89},"}\n",[40,921,922],{"class":42,"line":204},[40,923,117],{"emptyLinePlaceholder":116},[40,925,926,928,931,933,936,938,940,943],{"class":42,"line":242},[40,927,149],{"class":85},[40,929,930],{"class":89}," sku ",[40,932,155],{"class":85},[40,934,935],{"class":89}," (",[40,937,671],{"class":50},[40,939,192],{"class":89},[40,941,942],{"class":50},"\"CH-110\"",[40,944,556],{"class":89},[40,946,947,949,952,955],{"class":42,"line":592},[40,948,698],{"class":176},[40,950,951],{"class":89},"(sku, placement(thumbnail(photos[sku])[",[40,953,954],{"class":176},"1",[40,956,957],{"class":89},"]))\n",[10,959,960,961,964,965,968],{},"Using the same scale for ",[37,962,963],{},"x_scale"," and ",[37,966,967],{},"y_scale"," is what keeps products from being stretched — a chair squashed to fit a landscape box looks like a different chair. The offsets centre the picture horizontally and vertically, with a four-pixel margin, so landscape and portrait photos line up down the column.",[290,970,299,975,299,978,299,981,299,984,299,989,299,995,299,1001,299,1007,299,1013,299,1021,299,1025,299,1030,299,1034,299,1039,299,1042,299,1048,299,1053,299,1057,299,1061,299,1066,299,1069,299,1074,299,1077],{"viewBox":971,"role":293,"ariaLabelledBy":972,"xmlns":297,"style":298},"4 0 752 207",[973,974],"cat-fit-t","cat-fit-d",[301,976,977],{"id":973},"Stretching to fill versus fitting the box",[305,979,980],{"id":974},"Two panels. Stretching a photo to the cell size distorts portrait and landscape products differently. Fitting with one scale factor and centring keeps every product's proportions and lines them up.",[309,982],{"x":311,"y":312,"width":313,"height":983,"fill":315},"207",[309,985],{"x":318,"y":319,"width":986,"height":987,"rx":988,"fill":452,"stroke":453,"style":325},"351.0","175.0","14",[327,990,992],{"transform":991},"translate(182.5,29.0) scale(1.083)",[331,993],{"d":459,"fill":334,"stroke":460,"style":994},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[338,996,1000],{"x":997,"y":998,"style":999},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Stretch to cell",[42,1002],{"x1":1003,"y1":1004,"x2":1005,"y2":1004,"stroke":453,"style":1006},"36.0","91.0","355.0","stroke-width:1px",[338,1008,1012],{"x":1009,"y":1010,"style":1011},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","different x and y scale",[327,1014,1016],{"transform":1015},"translate(38.5,99.5) scale(0.625)",[331,1017],{"d":1018,"fill":334,"stroke":1019,"style":1020},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[338,1022,1024],{"x":1009,"y":1023,"style":1011},"137.0","portrait items squashed",[327,1026,1028],{"transform":1027},"translate(38.5,125.5) scale(0.625)",[331,1029],{"d":1018,"fill":334,"stroke":1019,"style":1020},[338,1031,1033],{"x":1009,"y":1032,"style":1011},"163.0","products look wrong",[327,1035,1037],{"transform":1036},"translate(38.5,151.5) scale(0.625)",[331,1038],{"d":1018,"fill":334,"stroke":1019,"style":1020},[309,1040],{"x":1041,"y":319,"width":986,"height":987,"rx":988,"fill":422,"stroke":423,"style":325},"389.0",[327,1043,1045],{"transform":1044},"translate(551.5,29.0) scale(1.083)",[331,1046],{"d":1047,"fill":334,"stroke":430,"style":994},"M5 12.5l4.5 4.5L19 7",[338,1049,1052],{"x":1050,"y":998,"style":1051},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Fit and centre",[42,1054],{"x1":1055,"y1":1004,"x2":1056,"y2":1004,"stroke":423,"style":1006},"405.0","724.0",[338,1058,1060],{"x":1059,"y":1010,"style":1011},"429.0","one scale for both axes",[327,1062,1064],{"transform":1063},"translate(407.5,99.5) scale(0.625)",[331,1065],{"d":1047,"fill":334,"stroke":423,"style":1020},[338,1067,1068],{"x":1059,"y":1023,"style":1011},"centred with offsets",[327,1070,1072],{"transform":1071},"translate(407.5,125.5) scale(0.625)",[331,1073],{"d":1047,"fill":334,"stroke":423,"style":1020},[338,1075,1076],{"x":1059,"y":1032,"style":1011},"proportions kept",[327,1078,1080],{"transform":1079},"translate(407.5,151.5) scale(0.625)",[331,1081],{"d":1047,"fill":334,"stroke":423,"style":1020},[25,1083,1085],{"id":1084},"step-3-build-the-catalogue","Step 3 — Build the catalogue",[10,1087,1088,1089,1092,1093,1096],{},"Write the product data with xlsxwriter, group rows by category with a heading row for each, and insert the thumbnails with ",[37,1090,1091],{},"object_position=1",", which tells Excel to move ",[14,1094,1095],{},"and resize"," each picture with its cell:",[30,1098,1100],{"className":76,"code":1099,"language":78,"meta":35,"style":35},"import xlsxwriter\n\nwb = xlsxwriter.Workbook(\"catalogue.xlsx\")\nws = wb.add_worksheet(\"Catalogue\")\nbold = wb.add_format({\"bold\": True, \"font_color\": \"#FFFFFF\", \"bg_color\": \"#4338CA\", \"valign\": \"vcenter\"})\ncat_fmt = wb.add_format({\"bold\": True, \"font_size\": 12, \"bottom\": 1})\ncell = wb.add_format({\"valign\": \"vcenter\", \"text_wrap\": True})\nprice = wb.add_format({\"valign\": \"vcenter\", \"num_format\": \"£#,##0.00\"})\nmissing = wb.add_format({\"valign\": \"vcenter\", \"align\": \"center\", \"font_color\": \"#9CA3AF\", \"italic\": True})\n\nws.set_column(\"A:A\", 18)\nws.set_column(\"B:B\", 12)\nws.set_column(\"C:C\", 26)\nws.set_column(\"D:D\", 11)\nws.write_row(0, 0, [\"Photo\", \"SKU\", \"Name\", \"Price\"], bold)\nrow = 1\nfor category, group in products.groupby(\"Category\", sort=True):\n    ws.write(row, 0, category, cat_fmt)\n    row += 1\n    for p in group.itertuples(index=False):\n        ws.set_row(row, BOX_H * 0.75 + 6)                 # points = pixels × 0.75\n        if p.SKU in photos:\n            buf, size = thumbnail(photos[p.SKU])\n            ws.insert_image(row, 0, f\"{p.SKU}.jpg\", {\"image_data\": buf, \"object_position\": 1, **placement(size)})\n        else:\n            ws.write(row, 0, \"no photo\", missing)\n        ws.write(row, 1, p.SKU, cell)\n        ws.write(row, 2, p.Name, cell)\n        ws.write(row, 3, p.Price, price)\n        row += 1\nws.autofilter(0, 0, row - 1, 3)\nws.freeze_panes(1, 0)\nwb.close()\nprint(\"rows written:\", row, f\"| file size {Path('catalogue.xlsx').stat().st_size \u002F 1024:.0f} KB\")\n",[37,1101,1102,1109,1113,1128,1143,1193,1228,1254,1281,1326,1330,1345,1358,1372,1386,1420,1431,1458,1469,1480,1503,1528,1545,1560,1610,1619,1635,1651,1661,1672,1682,1708,1722,1728],{"__ignoreMap":35},[40,1103,1104,1106],{"class":42,"line":43},[40,1105,93],{"class":85},[40,1107,1108],{"class":89}," xlsxwriter\n",[40,1110,1111],{"class":42,"line":99},[40,1112,117],{"emptyLinePlaceholder":116},[40,1114,1115,1118,1120,1123,1126],{"class":42,"line":113},[40,1116,1117],{"class":89},"wb ",[40,1119,126],{"class":85},[40,1121,1122],{"class":89}," xlsxwriter.Workbook(",[40,1124,1125],{"class":50},"\"catalogue.xlsx\"",[40,1127,135],{"class":89},[40,1129,1130,1133,1135,1138,1141],{"class":42,"line":120},[40,1131,1132],{"class":89},"ws ",[40,1134,126],{"class":85},[40,1136,1137],{"class":89}," wb.add_worksheet(",[40,1139,1140],{"class":50},"\"Catalogue\"",[40,1142,135],{"class":89},[40,1144,1145,1148,1150,1153,1156,1158,1160,1162,1165,1167,1170,1172,1175,1177,1180,1182,1185,1187,1190],{"class":42,"line":138},[40,1146,1147],{"class":89},"bold ",[40,1149,126],{"class":85},[40,1151,1152],{"class":89}," wb.add_format({",[40,1154,1155],{"class":50},"\"bold\"",[40,1157,860],{"class":89},[40,1159,641],{"class":176},[40,1161,192],{"class":89},[40,1163,1164],{"class":50},"\"font_color\"",[40,1166,860],{"class":89},[40,1168,1169],{"class":50},"\"#FFFFFF\"",[40,1171,192],{"class":89},[40,1173,1174],{"class":50},"\"bg_color\"",[40,1176,860],{"class":89},[40,1178,1179],{"class":50},"\"#4338CA\"",[40,1181,192],{"class":89},[40,1183,1184],{"class":50},"\"valign\"",[40,1186,860],{"class":89},[40,1188,1189],{"class":50},"\"vcenter\"",[40,1191,1192],{"class":89},"})\n",[40,1194,1195,1198,1200,1202,1204,1206,1208,1210,1213,1215,1217,1219,1222,1224,1226],{"class":42,"line":173},[40,1196,1197],{"class":89},"cat_fmt ",[40,1199,126],{"class":85},[40,1201,1152],{"class":89},[40,1203,1155],{"class":50},[40,1205,860],{"class":89},[40,1207,641],{"class":176},[40,1209,192],{"class":89},[40,1211,1212],{"class":50},"\"font_size\"",[40,1214,860],{"class":89},[40,1216,322],{"class":176},[40,1218,192],{"class":89},[40,1220,1221],{"class":50},"\"bottom\"",[40,1223,860],{"class":89},[40,1225,954],{"class":176},[40,1227,1192],{"class":89},[40,1229,1230,1233,1235,1237,1239,1241,1243,1245,1248,1250,1252],{"class":42,"line":204},[40,1231,1232],{"class":89},"cell ",[40,1234,126],{"class":85},[40,1236,1152],{"class":89},[40,1238,1184],{"class":50},[40,1240,860],{"class":89},[40,1242,1189],{"class":50},[40,1244,192],{"class":89},[40,1246,1247],{"class":50},"\"text_wrap\"",[40,1249,860],{"class":89},[40,1251,641],{"class":176},[40,1253,1192],{"class":89},[40,1255,1256,1259,1261,1263,1265,1267,1269,1271,1274,1276,1279],{"class":42,"line":242},[40,1257,1258],{"class":89},"price ",[40,1260,126],{"class":85},[40,1262,1152],{"class":89},[40,1264,1184],{"class":50},[40,1266,860],{"class":89},[40,1268,1189],{"class":50},[40,1270,192],{"class":89},[40,1272,1273],{"class":50},"\"num_format\"",[40,1275,860],{"class":89},[40,1277,1278],{"class":50},"\"£#,##0.00\"",[40,1280,1192],{"class":89},[40,1282,1283,1286,1288,1290,1292,1294,1296,1298,1301,1303,1306,1308,1310,1312,1315,1317,1320,1322,1324],{"class":42,"line":592},[40,1284,1285],{"class":89},"missing ",[40,1287,126],{"class":85},[40,1289,1152],{"class":89},[40,1291,1184],{"class":50},[40,1293,860],{"class":89},[40,1295,1189],{"class":50},[40,1297,192],{"class":89},[40,1299,1300],{"class":50},"\"align\"",[40,1302,860],{"class":89},[40,1304,1305],{"class":50},"\"center\"",[40,1307,192],{"class":89},[40,1309,1164],{"class":50},[40,1311,860],{"class":89},[40,1313,1314],{"class":50},"\"#9CA3AF\"",[40,1316,192],{"class":89},[40,1318,1319],{"class":50},"\"italic\"",[40,1321,860],{"class":89},[40,1323,641],{"class":176},[40,1325,1192],{"class":89},[40,1327,1328],{"class":42,"line":603},[40,1329,117],{"emptyLinePlaceholder":116},[40,1331,1332,1335,1338,1340,1343],{"class":42,"line":646},[40,1333,1334],{"class":89},"ws.set_column(",[40,1336,1337],{"class":50},"\"A:A\"",[40,1339,192],{"class":89},[40,1341,1342],{"class":176},"18",[40,1344,135],{"class":89},[40,1346,1347,1349,1352,1354,1356],{"class":42,"line":655},[40,1348,1334],{"class":89},[40,1350,1351],{"class":50},"\"B:B\"",[40,1353,192],{"class":89},[40,1355,322],{"class":176},[40,1357,135],{"class":89},[40,1359,1360,1362,1365,1367,1370],{"class":42,"line":660},[40,1361,1334],{"class":89},[40,1363,1364],{"class":50},"\"C:C\"",[40,1366,192],{"class":89},[40,1368,1369],{"class":176},"26",[40,1371,135],{"class":89},[40,1373,1374,1376,1379,1381,1384],{"class":42,"line":677},[40,1375,1334],{"class":89},[40,1377,1378],{"class":50},"\"D:D\"",[40,1380,192],{"class":89},[40,1382,1383],{"class":176},"11",[40,1385,135],{"class":89},[40,1387,1388,1391,1393,1395,1397,1400,1403,1405,1407,1409,1412,1414,1417],{"class":42,"line":695},[40,1389,1390],{"class":89},"ws.write_row(",[40,1392,312],{"class":176},[40,1394,192],{"class":89},[40,1396,312],{"class":176},[40,1398,1399],{"class":89},", [",[40,1401,1402],{"class":50},"\"Photo\"",[40,1404,192],{"class":89},[40,1406,227],{"class":50},[40,1408,192],{"class":89},[40,1410,1411],{"class":50},"\"Name\"",[40,1413,192],{"class":89},[40,1415,1416],{"class":50},"\"Price\"",[40,1418,1419],{"class":89},"], bold)\n",[40,1421,1423,1426,1428],{"class":42,"line":1422},16,[40,1424,1425],{"class":89},"row ",[40,1427,126],{"class":85},[40,1429,1430],{"class":176}," 1\n",[40,1432,1434,1436,1439,1441,1444,1447,1449,1452,1454,1456],{"class":42,"line":1433},17,[40,1435,149],{"class":85},[40,1437,1438],{"class":89}," category, group ",[40,1440,155],{"class":85},[40,1442,1443],{"class":89}," products.groupby(",[40,1445,1446],{"class":50},"\"Category\"",[40,1448,192],{"class":89},[40,1450,1451],{"class":615},"sort",[40,1453,126],{"class":85},[40,1455,641],{"class":176},[40,1457,556],{"class":89},[40,1459,1461,1464,1466],{"class":42,"line":1460},18,[40,1462,1463],{"class":89},"    ws.write(row, ",[40,1465,312],{"class":176},[40,1467,1468],{"class":89},", category, cat_fmt)\n",[40,1470,1472,1475,1478],{"class":42,"line":1471},19,[40,1473,1474],{"class":89},"    row ",[40,1476,1477],{"class":85},"+=",[40,1479,1430],{"class":176},[40,1481,1483,1486,1488,1490,1493,1496,1498,1501],{"class":42,"line":1482},20,[40,1484,1485],{"class":85},"    for",[40,1487,152],{"class":89},[40,1489,155],{"class":85},[40,1491,1492],{"class":89}," group.itertuples(",[40,1494,1495],{"class":615},"index",[40,1497,126],{"class":85},[40,1499,1500],{"class":176},"False",[40,1502,556],{"class":89},[40,1504,1506,1509,1511,1513,1516,1519,1522,1525],{"class":42,"line":1505},21,[40,1507,1508],{"class":89},"        ws.set_row(row, ",[40,1510,517],{"class":176},[40,1512,579],{"class":85},[40,1514,1515],{"class":176}," 0.75",[40,1517,1518],{"class":85}," +",[40,1520,1521],{"class":176}," 6",[40,1523,1524],{"class":89},")                 ",[40,1526,1527],{"class":531},"# points = pixels × 0.75\n",[40,1529,1531,1534,1537,1539,1542],{"class":42,"line":1530},22,[40,1532,1533],{"class":85},"        if",[40,1535,1536],{"class":89}," p.",[40,1538,347],{"class":176},[40,1540,1541],{"class":85}," in",[40,1543,1544],{"class":89}," photos:\n",[40,1546,1548,1551,1553,1556,1558],{"class":42,"line":1547},23,[40,1549,1550],{"class":89},"            buf, size ",[40,1552,126],{"class":85},[40,1554,1555],{"class":89}," thumbnail(photos[p.",[40,1557,347],{"class":176},[40,1559,674],{"class":89},[40,1561,1563,1566,1568,1570,1572,1574,1576,1579,1581,1583,1586,1589,1592,1595,1598,1600,1602,1604,1607],{"class":42,"line":1562},24,[40,1564,1565],{"class":89},"            ws.insert_image(row, ",[40,1567,312],{"class":176},[40,1569,192],{"class":89},[40,1571,249],{"class":85},[40,1573,717],{"class":50},[40,1575,256],{"class":255},[40,1577,1578],{"class":89},"p.",[40,1580,347],{"class":176},[40,1582,283],{"class":255},[40,1584,1585],{"class":50},".jpg\"",[40,1587,1588],{"class":89},", {",[40,1590,1591],{"class":50},"\"image_data\"",[40,1593,1594],{"class":89},": buf, ",[40,1596,1597],{"class":50},"\"object_position\"",[40,1599,860],{"class":89},[40,1601,954],{"class":176},[40,1603,192],{"class":89},[40,1605,1606],{"class":85},"**",[40,1608,1609],{"class":89},"placement(size)})\n",[40,1611,1613,1616],{"class":42,"line":1612},25,[40,1614,1615],{"class":85},"        else",[40,1617,1618],{"class":89},":\n",[40,1620,1622,1625,1627,1629,1632],{"class":42,"line":1621},26,[40,1623,1624],{"class":89},"            ws.write(row, ",[40,1626,312],{"class":176},[40,1628,192],{"class":89},[40,1630,1631],{"class":50},"\"no photo\"",[40,1633,1634],{"class":89},", missing)\n",[40,1636,1638,1641,1643,1646,1648],{"class":42,"line":1637},27,[40,1639,1640],{"class":89},"        ws.write(row, ",[40,1642,954],{"class":176},[40,1644,1645],{"class":89},", p.",[40,1647,347],{"class":176},[40,1649,1650],{"class":89},", cell)\n",[40,1652,1654,1656,1658],{"class":42,"line":1653},28,[40,1655,1640],{"class":89},[40,1657,553],{"class":176},[40,1659,1660],{"class":89},", p.Name, cell)\n",[40,1662,1664,1666,1669],{"class":42,"line":1663},29,[40,1665,1640],{"class":89},[40,1667,1668],{"class":176},"3",[40,1670,1671],{"class":89},", p.Price, price)\n",[40,1673,1675,1678,1680],{"class":42,"line":1674},30,[40,1676,1677],{"class":89},"        row ",[40,1679,1477],{"class":85},[40,1681,1430],{"class":176},[40,1683,1685,1688,1690,1692,1694,1697,1699,1702,1704,1706],{"class":42,"line":1684},31,[40,1686,1687],{"class":89},"ws.autofilter(",[40,1689,312],{"class":176},[40,1691,192],{"class":89},[40,1693,312],{"class":176},[40,1695,1696],{"class":89},", row ",[40,1698,233],{"class":85},[40,1700,1701],{"class":176}," 1",[40,1703,192],{"class":89},[40,1705,1668],{"class":176},[40,1707,135],{"class":89},[40,1709,1711,1714,1716,1718,1720],{"class":42,"line":1710},32,[40,1712,1713],{"class":89},"ws.freeze_panes(",[40,1715,954],{"class":176},[40,1717,192],{"class":89},[40,1719,312],{"class":176},[40,1721,135],{"class":89},[40,1723,1725],{"class":42,"line":1724},33,[40,1726,1727],{"class":89},"wb.close()\n",[40,1729,1731,1733,1735,1738,1741,1743,1746,1748,1751,1754,1757,1759,1761,1764,1766,1768],{"class":42,"line":1730},34,[40,1732,177],{"class":176},[40,1734,180],{"class":89},[40,1736,1737],{"class":50},"\"rows written:\"",[40,1739,1740],{"class":89},", row, ",[40,1742,249],{"class":85},[40,1744,1745],{"class":50},"\"| file size ",[40,1747,256],{"class":255},[40,1749,1750],{"class":89},"Path(",[40,1752,1753],{"class":50},"'catalogue.xlsx'",[40,1755,1756],{"class":89},").stat().st_size ",[40,1758,274],{"class":85},[40,1760,729],{"class":176},[40,1762,1763],{"class":85},":.0f",[40,1765,283],{"class":255},[40,1767,736],{"class":50},[40,1769,135],{"class":89},[10,1771,1772,1775],{},[37,1773,1774],{},"image_data"," takes the in-memory buffer, and the file name argument only labels the picture inside the workbook. Row heights are in points, and 0.75 points make a pixel at Excel's 96 dpi, so a 90-pixel box needs about 68 points plus a little margin. The desk lamp gets a grey italic \"no photo\" in its cell rather than an empty gap, which makes missing photos obvious to whoever reviews the catalogue before it goes out. The whole workbook comes to a small fraction of the photo folder's size. For comparison, insert the original photos at the same displayed size:",[30,1777,1779],{"className":76,"code":1778,"language":78,"meta":35,"style":35},"wb = xlsxwriter.Workbook(\"catalogue_full_size.xlsx\")\nws = wb.add_worksheet()\nfor r, (sku, path) in enumerate(sorted(photos.items()), start=1):\n    with Image.open(path) as im:\n        scale = min(BOX_W \u002F im.width, BOX_H \u002F im.height)\n    ws.set_row(r, BOX_H * 0.75 + 6)\n    ws.insert_image(r, 0, str(path), {\"x_scale\": scale, \"y_scale\": scale, \"object_position\": 1})\nwb.close()\nprint(f\"full-size photos: {Path('catalogue_full_size.xlsx').stat().st_size \u002F 1e6:.1f} MB\")\n",[37,1780,1781,1794,1803,1831,1842,1867,1884,1915,1919],{"__ignoreMap":35},[40,1782,1783,1785,1787,1789,1792],{"class":42,"line":43},[40,1784,1117],{"class":89},[40,1786,126],{"class":85},[40,1788,1122],{"class":89},[40,1790,1791],{"class":50},"\"catalogue_full_size.xlsx\"",[40,1793,135],{"class":89},[40,1795,1796,1798,1800],{"class":42,"line":99},[40,1797,1132],{"class":89},[40,1799,126],{"class":85},[40,1801,1802],{"class":89}," wb.add_worksheet()\n",[40,1804,1805,1807,1810,1812,1815,1817,1819,1822,1825,1827,1829],{"class":42,"line":113},[40,1806,149],{"class":85},[40,1808,1809],{"class":89}," r, (sku, path) ",[40,1811,155],{"class":85},[40,1813,1814],{"class":176}," enumerate",[40,1816,180],{"class":89},[40,1818,216],{"class":176},[40,1820,1821],{"class":89},"(photos.items()), ",[40,1823,1824],{"class":615},"start",[40,1826,126],{"class":85},[40,1828,954],{"class":176},[40,1830,556],{"class":89},[40,1832,1833,1835,1837,1839],{"class":42,"line":120},[40,1834,561],{"class":85},[40,1836,564],{"class":89},[40,1838,107],{"class":85},[40,1840,1841],{"class":89}," im:\n",[40,1843,1844,1847,1849,1851,1853,1855,1857,1860,1862,1864],{"class":42,"line":138},[40,1845,1846],{"class":89},"        scale ",[40,1848,126],{"class":85},[40,1850,794],{"class":176},[40,1852,180],{"class":89},[40,1854,512],{"class":176},[40,1856,801],{"class":85},[40,1858,1859],{"class":89}," im.width, ",[40,1861,517],{"class":176},[40,1863,801],{"class":85},[40,1865,1866],{"class":89}," im.height)\n",[40,1868,1869,1872,1874,1876,1878,1880,1882],{"class":42,"line":173},[40,1870,1871],{"class":89},"    ws.set_row(r, ",[40,1873,517],{"class":176},[40,1875,579],{"class":85},[40,1877,1515],{"class":176},[40,1879,1518],{"class":85},[40,1881,1521],{"class":176},[40,1883,135],{"class":89},[40,1885,1886,1889,1891,1893,1896,1899,1901,1903,1905,1907,1909,1911,1913],{"class":42,"line":204},[40,1887,1888],{"class":89},"    ws.insert_image(r, ",[40,1890,312],{"class":176},[40,1892,192],{"class":89},[40,1894,1895],{"class":176},"str",[40,1897,1898],{"class":89},"(path), {",[40,1900,843],{"class":50},[40,1902,846],{"class":89},[40,1904,849],{"class":50},[40,1906,846],{"class":89},[40,1908,1597],{"class":50},[40,1910,860],{"class":89},[40,1912,954],{"class":176},[40,1914,1192],{"class":89},[40,1916,1917],{"class":42,"line":242},[40,1918,1727],{"class":89},[40,1920,1921,1923,1925,1927,1930,1932,1934,1937,1939,1941,1943,1945,1947,1949],{"class":42,"line":592},[40,1922,177],{"class":176},[40,1924,180],{"class":89},[40,1926,249],{"class":85},[40,1928,1929],{"class":50},"\"full-size photos: ",[40,1931,256],{"class":255},[40,1933,1750],{"class":89},[40,1935,1936],{"class":50},"'catalogue_full_size.xlsx'",[40,1938,1756],{"class":89},[40,1940,274],{"class":85},[40,1942,277],{"class":176},[40,1944,280],{"class":85},[40,1946,283],{"class":255},[40,1948,286],{"class":50},[40,1950,135],{"class":89},[10,1952,1953],{},"It looks the same on screen and is several hundred times bigger — 8.9 MB against about 20 KB here — because every original is stored in full, down to pixels nobody will ever see at thumbnail size. With hundreds of products, that is the difference between a catalogue that can be emailed and one that cannot.",[25,1955,1957],{"id":1956},"step-4-why-object_position-matters","Step 4 — Why object_position matters",[10,1959,1960,1961,1964,1965,1967],{},"By default a picture in Excel is ",[14,1962,1963],{},"placed over"," the cells: sorting the rows or filtering some out leaves the picture where it was, now next to the wrong product. With ",[37,1966,1091],{}," the picture is attached to its cell and moves with it when the rows are sorted — and it is hidden along with its row when a filter hides that row. For a catalogue that people will sort by price or filter by category, that is the difference between a usable sheet and a misleading one:",[30,1969,1971],{"className":76,"code":1970,"language":78,"meta":35,"style":35},"import re\nimport zipfile\n\nxml = zipfile.ZipFile(\"catalogue.xlsx\").read(\"xl\u002Fdrawings\u002Fdrawing1.xml\").decode()\nprint(\"anchor tags:\", sorted(set(re.findall(r\"\u003Cxdr:\\w*Anchor[^>]*>\", xml))), \"| pictures:\", xml.count(\"\u003Cxdr:pic>\"))\n",[37,1972,1973,1980,1987,1991,2012],{"__ignoreMap":35},[40,1974,1975,1977],{"class":42,"line":43},[40,1976,93],{"class":85},[40,1978,1979],{"class":89}," re\n",[40,1981,1982,1984],{"class":42,"line":99},[40,1983,93],{"class":85},[40,1985,1986],{"class":89}," zipfile\n",[40,1988,1989],{"class":42,"line":113},[40,1990,117],{"emptyLinePlaceholder":116},[40,1992,1993,1996,1998,2001,2003,2006,2009],{"class":42,"line":120},[40,1994,1995],{"class":89},"xml ",[40,1997,126],{"class":85},[40,1999,2000],{"class":89}," zipfile.ZipFile(",[40,2002,1125],{"class":50},[40,2004,2005],{"class":89},").read(",[40,2007,2008],{"class":50},"\"xl\u002Fdrawings\u002Fdrawing1.xml\"",[40,2010,2011],{"class":89},").decode()\n",[40,2013,2014,2016,2018,2021,2023,2025,2027,2029,2032,2035,2038,2041,2043,2046,2049,2052,2055,2058,2060,2063,2066,2069,2072,2075],{"class":42,"line":138},[40,2015,177],{"class":176},[40,2017,180],{"class":89},[40,2019,2020],{"class":50},"\"anchor tags:\"",[40,2022,192],{"class":89},[40,2024,216],{"class":176},[40,2026,180],{"class":89},[40,2028,221],{"class":176},[40,2030,2031],{"class":89},"(re.findall(",[40,2033,2034],{"class":85},"r",[40,2036,2037],{"class":50},"\"\u003Cxdr:",[40,2039,2040],{"class":176},"\\w",[40,2042,824],{"class":85},[40,2044,2045],{"class":50},"Anchor",[40,2047,2048],{"class":176},"[",[40,2050,2051],{"class":85},"^",[40,2053,2054],{"class":255},">",[40,2056,2057],{"class":176},"]",[40,2059,824],{"class":85},[40,2061,2062],{"class":50},">\"",[40,2064,2065],{"class":89},", xml))), ",[40,2067,2068],{"class":50},"\"| pictures:\"",[40,2070,2071],{"class":89},", xml.count(",[40,2073,2074],{"class":50},"\"\u003Cxdr:pic>\"",[40,2076,2077],{"class":89},"))\n",[10,2079,2080,2081,2084,2085,2088,2089,2092,2093,964,2096,2092,2098,2101,2102,2105,2106,2109,2110,2113,2114,2118],{},"Every picture is stored as a plain ",[37,2082,2083],{},"twoCellAnchor"," — pinned to a start cell and an end cell — with no ",[37,2086,2087],{},"editAs"," attribute, which the file format defines as \"move and size with cells\". (xlsxwriter's ",[37,2090,2091],{},"object_position=2"," writes ",[37,2094,2095],{},"editAs=\"oneCell\"",[37,2097,1668],{},[37,2099,2100],{},"editAs=\"absolute\"",".) openpyxl's ",[37,2103,2104],{},"add_image"," writes pictures with a ",[37,2107,2108],{},"oneCellAnchor"," instead, which moves with the top-left cell but does not resize with it; for catalogues that users will sort and filter, xlsxwriter's ",[37,2111,2112],{},"object_position"," is the more reliable choice. ",[19,2115,2117],{"href":2116},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-images-inside-cells-with-xlsxwriter\u002F","Embed Images Inside Cells with xlsxwriter"," covers the newer in-cell pictures of Excel 365, which behave like cell values entirely.",[290,2120,299,2126,299,2129,299,2132,299,2137,299,2144,299,2149,299,2156,299,2161,299,2166,299,2169,299,2175,299,2179,299,2183,299,2187,299,2191,299,2195,299,2199,299,2203,299,2207,299,2209,299,2214,299,2218,299,2222,299,2224,299,2227,299,2230,299,2233,299,2235,299,2239,299,2242,299,2245,299,2247,299,2251,299,2254,299,2257,299,2260,299,2263,299,2265,299,2269,299,2271,299,2273,299,2275,299,2278,299,2281,299,2283,299,2286,299,2288,299,2291,299,2293,299,2296,299,2298,299,2301,299,2304,299,2307,299,2309,299,2313,299,2315,299,2318,299,2320,299,2323,299,2325,299,2328,299,2331,299,2335,299,2337,299,2340,299,2342,299,2344,299,2346,299,2349,299,2353,299,2355,299,2357,299,2359,299,2362,299,2364,299,2367,299,2369],{"viewBox":2121,"role":293,"ariaLabelledBy":2122,"xmlns":297,"style":2125},"146 0 468 254",[2123,2124],"cat-sheet-t","cat-sheet-d","width:100%;max-width:468px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[301,2127,2128],{"id":2123},"The catalogue sheet",[305,2130,2131],{"id":2124},"A worksheet with a photo column, SKU, name and price, grouped under category headings. Each photo sits centred in its cell; the desk lamp shows a no photo placeholder.",[309,2133],{"x":2134,"y":312,"width":2135,"height":2136,"fill":315},"146","468","254",[309,2138],{"x":2139,"y":319,"width":2140,"height":2141,"rx":2142,"fill":323,"stroke":324,"style":2143},"162.3","435.4","30.0","8","stroke-width:1.5px",[309,2145],{"x":2139,"y":2146,"width":2140,"height":2147,"rx":312,"fill":323,"stroke":324,"style":2148},"38.0","8.0","stroke-width:0px",[327,2150,2152],{"transform":2151},"translate(172.3,23.0) scale(0.667)",[331,2153],{"d":2154,"fill":334,"stroke":430,"style":2155},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[338,2157,2160],{"x":2158,"y":1003,"style":2159},"194.3","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","catalogue.xlsx",[309,2162],{"x":2139,"y":2163,"width":2164,"height":2165,"rx":312,"fill":323,"stroke":324,"style":1006},"46.0","32.0","24.0",[309,2167],{"x":2158,"y":2163,"width":2168,"height":2165,"rx":312,"fill":323,"stroke":324,"style":1006},"92.4",[338,2170,2174],{"x":2171,"y":2172,"style":2173},"240.5","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[309,2176],{"x":2177,"y":2163,"width":2178,"height":2165,"rx":312,"fill":323,"stroke":324,"style":1006},"286.7","85.0",[338,2180,2182],{"x":2181,"y":2172,"style":2173},"329.3","B",[309,2184],{"x":2185,"y":2163,"width":2186,"height":2165,"rx":312,"fill":323,"stroke":324,"style":1006},"371.8","138.1",[338,2188,2190],{"x":2189,"y":2172,"style":2173},"440.8","C",[309,2192],{"x":2193,"y":2163,"width":2194,"height":2165,"rx":312,"fill":323,"stroke":324,"style":1006},"509.8","87.8",[338,2196,2198],{"x":2197,"y":2172,"style":2173},"553.8","D",[309,2200],{"x":2139,"y":2201,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"70.0","28.0",[338,2204,954],{"x":2205,"y":2206,"style":2173},"178.3","89.0",[309,2208],{"x":2158,"y":2201,"width":2168,"height":2202,"rx":312,"fill":367,"stroke":358,"style":2143},[338,2210,2213],{"x":2211,"y":2206,"style":2212},"233.5","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Photo",[309,2215],{"x":2216,"y":998,"width":2217,"height":2217,"rx":553,"fill":323,"stroke":324,"style":1006},"268.7","12.0",[360,2219],{"points":2220,"fill":2221},"271.2,82.5 278.2,82.5 274.7,86.5","#5b6780",[309,2223],{"x":2177,"y":2201,"width":2178,"height":2202,"rx":312,"fill":367,"stroke":358,"style":2143},[338,2225,347],{"x":2226,"y":2206,"style":2212},"322.3",[309,2228],{"x":2229,"y":998,"width":2217,"height":2217,"rx":553,"fill":323,"stroke":324,"style":1006},"353.8",[360,2231],{"points":2232,"fill":2221},"356.3,82.5 363.3,82.5 359.8,86.5",[309,2234],{"x":2185,"y":2201,"width":2186,"height":2202,"rx":312,"fill":367,"stroke":358,"style":2143},[338,2236,2238],{"x":2237,"y":2206,"style":2212},"433.8","Name",[309,2240],{"x":2241,"y":998,"width":2217,"height":2217,"rx":553,"fill":323,"stroke":324,"style":1006},"491.8",[360,2243],{"points":2244,"fill":2221},"494.3,82.5 501.3,82.5 497.8,86.5",[309,2246],{"x":2193,"y":2201,"width":2194,"height":2202,"rx":312,"fill":367,"stroke":358,"style":2143},[338,2248,2250],{"x":2249,"y":2206,"style":2212},"546.8","Price",[309,2252],{"x":2253,"y":998,"width":2217,"height":2217,"rx":553,"fill":323,"stroke":324,"style":1006},"579.7",[360,2255],{"points":2256,"fill":2221},"582.2,82.5 589.2,82.5 585.7,86.5",[309,2258],{"x":2139,"y":2259,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"98.0",[338,2261,553],{"x":2205,"y":2262,"style":2173},"117.0",[309,2264],{"x":2158,"y":2259,"width":2168,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2266,2268],{"x":2171,"y":2262,"style":2267},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Lighting",[309,2270],{"x":2177,"y":2259,"width":2178,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2272],{"x":2185,"y":2259,"width":2186,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2274],{"x":2193,"y":2259,"width":2194,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2276],{"x":2139,"y":2277,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"126.0",[338,2279,1668],{"x":2205,"y":2280,"style":2173},"145.0",[309,2282],{"x":2158,"y":2277,"width":2168,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2284,2285],{"x":2171,"y":2280,"style":2267},"[photo]",[309,2287],{"x":2177,"y":2277,"width":2178,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2289,2290],{"x":2181,"y":2280,"style":2267},"LP-300",[309,2292],{"x":2185,"y":2277,"width":2186,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2294,2295],{"x":2189,"y":2280,"style":2267},"Brass floor lamp",[309,2297],{"x":2193,"y":2277,"width":2194,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2299,2300],{"x":2197,"y":2280,"style":2267},"£175.00",[309,2302],{"x":2139,"y":2303,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"154.0",[338,2305,311],{"x":2205,"y":2306,"style":2173},"173.0",[309,2308],{"x":2158,"y":2303,"width":2168,"height":2202,"rx":312,"fill":452,"stroke":453,"style":2143},[338,2310,2312],{"x":2171,"y":2306,"style":2311},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","no photo",[309,2314],{"x":2177,"y":2303,"width":2178,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2316,2317],{"x":2181,"y":2306,"style":2267},"LP-310",[309,2319],{"x":2185,"y":2303,"width":2186,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2321,2322],{"x":2189,"y":2306,"style":2267},"Desk lamp",[309,2324],{"x":2193,"y":2303,"width":2194,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2326,2327],{"x":2197,"y":2306,"style":2267},"£39.90",[309,2329],{"x":2139,"y":2330,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"182.0",[338,2332,2334],{"x":2205,"y":2333,"style":2173},"201.0","5",[309,2336],{"x":2158,"y":2330,"width":2168,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2338,2339],{"x":2171,"y":2333,"style":2267},"Seating",[309,2341],{"x":2177,"y":2330,"width":2178,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2343],{"x":2185,"y":2330,"width":2186,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2345],{"x":2193,"y":2330,"width":2194,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[309,2347],{"x":2139,"y":2348,"width":2164,"height":2202,"rx":312,"fill":323,"stroke":324,"style":1006},"210.0",[338,2350,2352],{"x":2205,"y":2351,"style":2173},"229.0","6",[309,2354],{"x":2158,"y":2348,"width":2168,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2356,2285],{"x":2171,"y":2351,"style":2267},[309,2358],{"x":2177,"y":2348,"width":2178,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2360,2361],{"x":2181,"y":2351,"style":2267},"CH-100",[309,2363],{"x":2185,"y":2348,"width":2186,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2365,2366],{"x":2189,"y":2351,"style":2267},"Oak dining chair",[309,2368],{"x":2193,"y":2348,"width":2194,"height":2202,"rx":312,"fill":315,"stroke":324,"style":1006},[338,2370,2371],{"x":2197,"y":2351,"style":2267},"£89.00",[25,2373,2375],{"id":2374},"common-pitfalls","Common pitfalls",[2377,2378,2379,2387,2393,2402,2408],"ul",{},[2380,2381,2382,2386],"li",{},[2383,2384,2385],"strong",{},"Inserting full-size photos."," Excel keeps the original resolution; shrink first or the file balloons, as the 8.9 MB comparison showed.",[2380,2388,2389,2392],{},[2383,2390,2391],{},"Different x and y scales."," Products look distorted; use one scale factor and centre with offsets.",[2380,2394,2395,2398,2399,2401],{},[2383,2396,2397],{},"Pictures that stay put when sorting."," Use ",[37,2400,1091],{}," so pictures move and size with their cells.",[2380,2403,2404,2407],{},[2383,2405,2406],{},"Row heights in the wrong unit."," Heights are points; multiply pixels by 0.75.",[2380,2409,2410,2413],{},[2383,2411,2412],{},"Photos matched by position."," Match by SKU, and report products without photos and photos without products.",[25,2415,2417],{"id":2416},"conclusion","Conclusion",[10,2419,2420,2421,2424,2425,2427],{},"Match photos to products by SKU, shrink each to about twice its display size with Pillow's ",[37,2422,2423],{},"thumbnail",", and insert it with one scale factor and centring offsets so every product keeps its proportions in a fixed cell box. Write the catalogue with xlsxwriter using ",[37,2426,1091],{},", so pictures move and hide with their rows when the sheet is sorted or filtered, mark missing photos visibly, and check the file size against the photo folder.",[25,2429,2431],{"id":2430},"frequently-asked-questions","Frequently asked questions",[10,2433,2434,2437],{},[2383,2435,2436],{},"How do I insert pictures into Excel rows with Python?","\nUse xlsxwriter's insert_image(row, col, name, {\"image_data\": buffer, ...}) with the picture in a BytesIO buffer, or openpyxl's add_image. Set the row height to fit the picture and scale it with x_scale and y_scale.",[10,2439,2440,2443],{},[2383,2441,2442],{},"Why is my Excel file with images so large?","\nExcel stores every picture at its original resolution, whatever size it is displayed at. Resize the images with Pillow before inserting them, to around twice the displayed size.",[10,2445,2446,2449],{},[2383,2447,2448],{},"How do I make images move with the rows when sorting?","\nIn xlsxwriter pass \"object_position\": 1 in the insert_image options, which anchors the picture to its cell so it moves and resizes with it and is hidden when its row is filtered out.",[10,2451,2452,2455],{},[2383,2453,2454],{},"How do I keep images from being stretched?","\nCompute one scale factor as the smaller of box width ÷ image width and box height ÷ image height, use it for both x_scale and y_scale, and centre the image with x_offset and y_offset.",[25,2457,2459],{"id":2458},"related","Related",[2377,2461,2462,2468,2475,2482],{},[2380,2463,2464,2465,2467],{},"Up one level: ",[19,2466,22],{"href":21}," — pictures in and out of workbooks.",[2380,2469,2470,2474],{},[19,2471,2473],{"href":2472},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl\u002F","Resize and Position Images in Excel with openpyxl"," — anchoring and scaling in openpyxl.",[2380,2476,2477,2481],{},[19,2478,2480],{"href":2479},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fgenerate-qr-codes-and-barcodes-in-excel-with-python\u002F","Generate QR Codes and Barcodes in Excel with Python"," — one generated image per row.",[2380,2483,2484,2488],{},[19,2485,2487],{"href":2486},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fextract-images-from-an-excel-file-with-python\u002F","Extract Images from an Excel File with Python"," — getting pictures back out.",[2490,2491,2492],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":35,"searchDepth":99,"depth":99,"links":2494},[2495,2496,2497,2498,2499,2500,2501,2502,2503],{"id":27,"depth":99,"text":28},{"id":474,"depth":99,"text":475},{"id":755,"depth":99,"text":756},{"id":1084,"depth":99,"text":1085},{"id":1956,"depth":99,"text":1957},{"id":2374,"depth":99,"text":2375},{"id":2416,"depth":99,"text":2417},{"id":2430,"depth":99,"text":2431},{"id":2458,"depth":99,"text":2459},"2026-09-29","Turn a product list and a folder of photos into a catalogue workbook: shrink each photo to a thumbnail before inserting it, keep its aspect ratio, centre it in a fixed-size cell, handle products without a photo, group by category, and anchor images so they move and hide with their rows when the sheet is sorted or filtered.","md",[2508,2510,2512,2514],{"q":2436,"a":2509},"Use xlsxwriter's insert_image(row, col, name, {\"image_data\": buffer, ...}) with the picture in a BytesIO buffer, or openpyxl's add_image. Set the row height to fit the picture and scale it with x_scale and y_scale.",{"q":2442,"a":2511},"Excel stores every picture at its original resolution, whatever size it is displayed at. Resize the images with Pillow before inserting them, to around twice the displayed size.",{"q":2448,"a":2513},"In xlsxwriter pass \"object_position\": 1 in the insert_image options, which anchors the picture to its cell so it moves and resizes with it and is hidden when its row is filtered out.",{"q":2454,"a":2515},"Compute one scale factor as the smaller of box width ÷ image width and box height ÷ image height, use it for both x_scale and y_scale, and centre the image with x_offset and y_offset.","build a product catalog with images in excel",null,{"breadcrumb":2519},[2520,2522,2525],{"name":2521,"item":274},"Home",{"name":2523,"item":2524},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel",{"title":2528,"description":2529},"Excel Product Catalogue with Pictures from Python","Create a product catalogue in Excel with Python: resize photos with Pillow, insert with xlsxwriter insert_image and object_position so images follow sorting and filtering, keep aspect ratios, handle missing photos and keep the file small.","build-a-product-catalog-with-images-in-excel","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fbuild-a-product-catalog-with-images-in-excel\u002Findex",[2533,2534],"charts-and-images","report-layouts","how-to","PxfsxsNe20_Sy0QKiBBg5kcpSizZ0iqBBYRy802pQbY",[2538,2542],{"title":2539,"path":2540,"stem":2541,"children":-1},"Add a Logo Image to an Excel Report with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fadd-logo-image-to-excel-report-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fadd-logo-image-to-excel-report-with-openpyxl\u002Findex",{"title":2543,"path":2544,"stem":2545,"children":-1},"Embed a matplotlib Chart in an Excel Report","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002Findex",[2547,2549],{"path":2548,"title":2523},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2550,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel",[],[2553,2556,2559,2562,2565,2568],{"path":2554,"title":2555,"kind":2535},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":2557,"title":2558,"kind":2535},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":2560,"title":2561,"kind":2535},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template","Keep Charts and Images When Filling an Excel Template",{"path":2563,"title":2564,"kind":2535},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","Add a Chart to an Excel File with xlsxwriter",{"path":2566,"title":2567,"kind":2535},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter","Add Sparklines to an Excel Report with xlsxwriter",{"path":2569,"title":2570,"kind":2535},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python","Build a Waterfall Chart in Excel with Python",1790972116626]