Advertisement
deedspool

Merge images for Cộng đồng

Apr 21st, 2025
358
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React, { useState, useRef } from 'react';
  2. import styled from 'styled-components';
  3.  
  4. const Container = styled.div`
  5.   padding: 20px;
  6.   display: flex;
  7.   flex-direction: column;
  8.   gap: 20px;
  9.   max-width: 800px;
  10.   margin: 0 auto;
  11. `;
  12.  
  13. const TextArea = styled.textarea`
  14.   width: 100%;
  15.   min-height: 150px;
  16.   padding: 10px;
  17.   border: 1px solid #ccc;
  18.   border-radius: 4px;
  19.   resize: vertical;
  20. `;
  21.  
  22. const InputGroup = styled.div`
  23.   display: flex;
  24.   gap: 10px;
  25.   align-items: center;
  26. `;
  27.  
  28. const NumberInput = styled.input`
  29.   width: 100px;
  30.   padding: 8px;
  31.   border: 1px solid #ccc;
  32.   border-radius: 4px;
  33. `;
  34.  
  35. const TextInput = styled.input`
  36.   width: 200px;
  37.   padding: 8px;
  38.   border: 1px solid #ccc;
  39.   border-radius: 4px;
  40. `;
  41.  
  42. const ColorInput = styled.input`
  43.   width: 50px;
  44.   height: 30px;
  45.   padding: 0;
  46.   border: 1px solid #ccc;
  47.   border-radius: 4px;
  48. `;
  49.  
  50. const Select = styled.select`
  51.   padding: 8px;
  52.   border: 1px solid #ccc;
  53.   border-radius: 4px;
  54. `;
  55.  
  56. const FileInput = styled.input`
  57.   padding: 8px;
  58. `;
  59.  
  60. const Button = styled.button`
  61.   width: 100%;
  62.   padding: 10px 20px;
  63.   background-color: #007bff;
  64.   color: white;
  65.   border: none;
  66.   border-radius: 4px;
  67.   cursor: pointer;
  68.   &:hover {
  69.     background-color: #0056b3;
  70.   }
  71. `;
  72.  
  73. const PreviewContainer = styled.div`
  74.   margin-top: 20px;
  75.   position: relative;
  76. `;
  77.  
  78. const PreviewImage = styled.img`
  79.   max-width: 100%;
  80.   display: block;
  81. `;
  82.  
  83. interface TextOverlayProps {
  84.   top: number;
  85.   left: number;
  86.   fontSize: number;
  87.   color: string;
  88.   fontWeight: number;
  89. }
  90.  
  91. const TextOverlay = styled.div<TextOverlayProps>`
  92.   position: absolute;
  93.   top: ${(props: TextOverlayProps) => props.top}px;
  94.   left: ${(props: TextOverlayProps) => props.left}px;
  95.   color: ${(props: TextOverlayProps) => props.color};
  96.   font-size: ${(props: TextOverlayProps) => props.fontSize}px;
  97.   font-weight: ${(props: TextOverlayProps) => props.fontWeight};
  98.   white-space: nowrap;
  99. `;
  100.  
  101. const Home = () => {
  102.   const [communityName, setCommunityName] = useState('');
  103.   const [image, setImage] = useState<File | null>(null);
  104.   const [top, setTop] = useState(0);
  105.   const [left, setLeft] = useState(0);
  106.   const [fontSize, setFontSize] = useState(16);
  107.   const [textColor, setTextColor] = useState('#000000');
  108.   const [fontWeight, setFontWeight] = useState(400);
  109.   const [additionalText, setAdditionalText] = useState('');
  110.   const [additionalTop, setAdditionalTop] = useState(0);
  111.   const [additionalLeft, setAdditionalLeft] = useState(0);
  112.   const [additionalFontSize, setAdditionalFontSize] = useState(16);
  113.   const [additionalTextColor, setAdditionalTextColor] = useState('#000000');
  114.   const [additionalFontWeight, setAdditionalFontWeight] = useState(400);
  115.   const [previewUrl, setPreviewUrl] = useState<string | null>(null);
  116.   const [textLines, setTextLines] = useState<string[]>([]);
  117.   const canvasRef = useRef<HTMLCanvasElement>(null);
  118.  
  119.   const fontWeights = [300, 400, 500, 700, 800, 900];
  120.  
  121.   const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  122.     if (e.target.files && e.target.files[0]) {
  123.       const file = e.target.files[0];
  124.       setImage(file);
  125.       setPreviewUrl(URL.createObjectURL(file));
  126.     }
  127.   };
  128.  
  129.   const processText = () => {
  130.     const lines = communityName.split('\n').filter(line => line.trim());
  131.     setTextLines(lines);
  132.   };
  133.  
  134.   const exportImages = async () => {
  135.     if (!image || !previewUrl) return;
  136.  
  137.     const img = new Image();
  138.     img.src = previewUrl;
  139.  
  140.     await new Promise((resolve) => {
  141.       img.onload = resolve;
  142.     });
  143.  
  144.     for (let i = 0; i < textLines.length; i++) {
  145.       const canvas = document.createElement('canvas');
  146.       canvas.width = img.width;
  147.       canvas.height = img.height;
  148.       const ctx = canvas.getContext('2d');
  149.      
  150.       if (!ctx) return;
  151.  
  152.       // Draw the original image
  153.       ctx.drawImage(img, 0, 0);
  154.  
  155.       // Draw the additional text
  156.       ctx.font = `${additionalFontWeight} ${additionalFontSize}px GFF Latin`;
  157.       ctx.fillStyle = additionalTextColor;
  158.       ctx.fillText(additionalText, additionalLeft, additionalTop + additionalFontSize + 10);
  159.  
  160.       // Draw only the current line of text
  161.       ctx.font = `${fontWeight} ${fontSize}px Arial`;
  162.       ctx.fillStyle = textColor;
  163.       ctx.fillText(textLines[i], left, top + fontSize + 10);
  164.  
  165.       // Create download link
  166.       const link = document.createElement('a');
  167.       link.download = `image_${i + 1}.png`;
  168.       link.href = canvas.toDataURL('image/png');
  169.       link.click();
  170.     }
  171.   };
  172.  
  173.   return (
  174.     <Container>
  175.       <TextArea
  176.         name="community_name"
  177.         value={communityName}
  178.         onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setCommunityName(e.target.value)}
  179.         placeholder="Enter text (one line per item)"
  180.       />
  181.    
  182.     <InputGroup>
  183.       <FileInput
  184.         type="file"
  185.         name="image"
  186.         accept="image/*"
  187.         onChange={handleImageChange}
  188.       />
  189.       </InputGroup>
  190.       <div className="row">
  191.         <div className="col-6">
  192.  
  193.         <InputGroup>
  194.           <label>Font Size:</label>
  195.           <NumberInput
  196.             type="number"
  197.             name="font_size"
  198.             value={fontSize}
  199.             onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFontSize(Number(e.target.value))}
  200.           />
  201.         </InputGroup>
  202.  
  203.         <InputGroup>
  204.           <label>Text Color:</label>
  205.           <ColorInput
  206.             type="color"
  207.             value={textColor}
  208.             onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTextColor(e.target.value)}
  209.           />
  210.           <label>Font Weight:</label>
  211.           <Select
  212.             value={fontWeight}
  213.             onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setFontWeight(Number(e.target.value))}
  214.           >
  215.             {fontWeights.map(weight => (
  216.               <option key={weight} value={weight}>{weight}</option>
  217.             ))}
  218.           </Select>
  219.         </InputGroup>
  220.  
  221.         <InputGroup>
  222.           <label>Text Position (Top):</label>
  223.           <NumberInput
  224.             type="number"
  225.             name="top"
  226.             value={top}
  227.             onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTop(Number(e.target.value))}
  228.           />
  229.           <label>Left:</label>
  230.           <NumberInput
  231.             type="number"
  232.             name="left"
  233.             value={left}
  234.             onChange={(e: React.ChangeEvent<HTMLInputElement>) => setLeft(Number(e.target.value))}
  235.           />
  236.         </InputGroup>
  237.         </div>
  238.  
  239.         <div className="col-6">
  240.           <InputGroup>
  241.             <label>Additional Text:</label>
  242.             <TextInput
  243.               type="text"
  244.               value={additionalText}
  245.               onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAdditionalText(e.target.value)}
  246.               placeholder="Enter additional text"
  247.             />
  248.           </InputGroup>
  249.  
  250.           <InputGroup>
  251.             <label>Additional Font Size:</label>
  252.             <NumberInput
  253.               type="number"
  254.               name="font_size_text"
  255.               value={additionalFontSize}
  256.               onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAdditionalFontSize(Number(e.target.value))}
  257.             />
  258.           </InputGroup>
  259.  
  260.           <InputGroup>
  261.             <label>Additional Text Color:</label>
  262.             <ColorInput
  263.               type="color"
  264.               value={additionalTextColor}
  265.               onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAdditionalTextColor(e.target.value)}
  266.             />
  267.             <label>Font Weight:</label>
  268.             <Select
  269.               value={additionalFontWeight}
  270.               onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setAdditionalFontWeight(Number(e.target.value))}
  271.             >
  272.               {fontWeights.map(weight => (
  273.                 <option key={weight} value={weight}>{weight}</option>
  274.               ))}
  275.             </Select>
  276.           </InputGroup>
  277.  
  278.           <InputGroup>
  279.             <label>Additional Text Position (Top):</label>
  280.             <NumberInput
  281.               type="number"
  282.               name="additional_top"
  283.               value={additionalTop}
  284.               onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAdditionalTop(Number(e.target.value))}
  285.             />
  286.             <label>Left:</label>
  287.             <NumberInput
  288.               type="number"
  289.               name="additional_left"
  290.               value={additionalLeft}
  291.               onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAdditionalLeft(Number(e.target.value))}
  292.             />
  293.           </InputGroup>
  294.         </div>
  295.       </div>
  296.  
  297.       <div className="row">
  298.         <div className="col-6">
  299.           <Button onClick={processText}>Process Text</Button>
  300.         </div>
  301.         <div className="col-6">
  302.           <Button onClick={exportImages}>Export Images</Button>
  303.         </div>
  304.       </div>
  305.  
  306.       {previewUrl && (
  307.         <PreviewContainer>
  308.           <PreviewImage src={previewUrl} alt="Preview" />
  309.           {textLines.map((line, index) => (
  310.             <TextOverlay
  311.               key={index}
  312.               top={top}
  313.               left={left}
  314.               fontSize={fontSize}
  315.               color={textColor}
  316.               fontWeight={fontWeight}
  317.             >
  318.               {line}
  319.             </TextOverlay>
  320.           ))}
  321.           {additionalText && (
  322.             <TextOverlay
  323.               top={additionalTop}
  324.               left={additionalLeft}
  325.               fontSize={additionalFontSize}
  326.               color={additionalTextColor}
  327.               fontWeight={additionalFontWeight}
  328.             >
  329.               {additionalText}
  330.             </TextOverlay>
  331.           )}
  332.         </PreviewContainer>
  333.       )}
  334.     </Container>
  335.   );
  336. };
  337.  
  338. export default Home;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement